diff --git a/.github/workflows/daily_benchmark.yaml b/.github/workflows/daily_benchmark.yaml
index 4cded7b0c..aa2686977 100644
--- a/.github/workflows/daily_benchmark.yaml
+++ b/.github/workflows/daily_benchmark.yaml
@@ -16,7 +16,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
diff --git a/.github/workflows/diff.yaml b/.github/workflows/diff.yaml
index 340d11711..411bed79c 100644
--- a/.github/workflows/diff.yaml
+++ b/.github/workflows/diff.yaml
@@ -27,7 +27,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -65,7 +65,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -96,7 +96,7 @@ jobs:
 
       - name: Python code analysis
         run: |
-          CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only)
+          CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only --diff-filter=d)
           for file in ${CHANGED_FILES}; do
             echo ${file}
             if [[ ${file} == *.py ]]; then
@@ -137,9 +137,9 @@ jobs:
           tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
 
       - name: Save code coverage
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: "Code coverage"
+          name: "Code coverage(Code analysis)"
           path: tools/github/generated/code_coverage.tar.gz
 
       - name: Run clang-tidy
@@ -162,7 +162,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -208,9 +208,9 @@ jobs:
           ./cppcheck_and_clang_format diff
 
       - name: Save cppcheck and clang-format errors
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: "Code coverage"
+          name: "Code coverage(Debug build)"
           path: tools/github/cppcheck_and_clang_format.txt
 
   release_build:
@@ -223,16 +223,12 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
           fetch-depth: 0
 
-      - name: Check e2e service dependencies
-        run: |
-          cd tests/e2e
-          ./dependency_check.sh
 
       - name: Build release binaries
         run: |
@@ -255,7 +251,7 @@ jobs:
           ./continuous_integration
 
       - name: Save quality assurance status
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "GQL Behave Status"
           path: |
@@ -271,7 +267,16 @@ jobs:
           cd build
           ctest -R memgraph__unit --output-on-failure -j$THREADS
 
+      - name: Ensure Kafka and Pulsar are up
+        if: false
+        run: |
+          cd tests/e2e/streams/kafka
+          docker-compose up -d
+          cd ../pulsar
+          docker-compose up -d
+
       - name: Run e2e tests
+        if: false
         run: |
           cd tests
           ./setup.sh /opt/toolchain-v4/activate
@@ -279,6 +284,14 @@ jobs:
           cd e2e
           ./run.sh
 
+      - name: Ensure Kafka and Pulsar are down
+        if: false
+        run: |
+          cd tests/e2e/streams/kafka
+          docker-compose down
+          cd ../pulsar
+          docker-compose down
+
       - name: Run stress test (plain)
         run: |
           cd tests/stress
@@ -312,16 +325,128 @@ jobs:
           cpack -G DEB --config ../CPackConfig.cmake
 
       - name: Save enterprise DEB package
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "Enterprise DEB package"
           path: build/output/memgraph*.deb
 
       - name: Save test data
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         if: always()
         with:
-          name: "Test data"
+          name: "Test data(Release build)"
+          path: |
+            # multiple paths could be defined
+            build/logs
+
+  experimental_build_ha:
+    name: "High availability build"
+    runs-on: [self-hosted, Linux, X64, Diff]
+    env:
+      THREADS: 24
+      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
+      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build release binaries
+        run: |
+          source /opt/toolchain-v4/activate
+          ./init
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=Release -DMG_EXPERIMENTAL_HIGH_AVAILABILITY=ON ..
+          make -j$THREADS
+      - name: Run unit tests
+        run: |
+          source /opt/toolchain-v4/activate
+          cd build
+          ctest -R memgraph__unit --output-on-failure -j$THREADS
+      - name: Run e2e tests
+        if: false
+        run: |
+          cd tests
+          ./setup.sh /opt/toolchain-v4/activate
+          source ve3/bin/activate_e2e
+          cd e2e
+          ./run.sh "Coordinator"
+          ./run.sh "Client initiated failover"
+          ./run.sh "Uninitialized cluster"
+      - name: Save test data
+        uses: actions/upload-artifact@v4
+        if: always()
+        with:
+          name: "Test data(High availability build)"
+          path: |
+            # multiple paths could be defined
+            build/logs
+
+  experimental_build_mt:
+    name: "MultiTenancy replication build"
+    runs-on: [self-hosted, Linux, X64, Diff]
+    env:
+      THREADS: 24
+      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
+      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+
+      - name: Build release binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Initialize dependencies.
+          ./init
+
+          # Build MT replication experimental binaries.
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=Release -D MG_EXPERIMENTAL_REPLICATION_MULTITENANCY=ON ..
+          make -j$THREADS
+
+      - name: Run unit tests
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Run unit tests.
+          cd build
+          ctest -R memgraph__unit --output-on-failure -j$THREADS
+
+      - name: Run e2e tests
+        if: false
+        run: |
+          cd tests
+          ./setup.sh /opt/toolchain-v4/activate
+          source ve3/bin/activate_e2e
+          cd e2e
+
+          # Just the replication based e2e tests
+          ./run.sh "Replicate multitenancy"
+          ./run.sh "Show"
+          ./run.sh "Show while creating invalid state"
+          ./run.sh "Delete edge replication"
+          ./run.sh "Read-write benchmark"
+          ./run.sh "Index replication"
+          ./run.sh "Constraints"
+
+      - name: Save test data
+        uses: actions/upload-artifact@v4
+        if: always()
+        with:
+          name: "Test data(MultiTenancy replication build)"
           path: |
             # multiple paths could be defined
             build/logs
@@ -337,7 +462,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -354,13 +479,18 @@ jobs:
           cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
           make -j$THREADS memgraph
 
+      - name: Refresh Jepsen Cluster
+        run: |
+          cd tests/jepsen
+          ./run.sh cluster-refresh
+
       - name: Run Jepsen tests
         run: |
           cd tests/jepsen
           ./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
 
       - name: Save Jepsen report
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         if: ${{ always() }}
         with:
           name: "Jepsen Report"
@@ -376,7 +506,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
diff --git a/.github/workflows/full_clang_tidy.yaml b/.github/workflows/full_clang_tidy.yaml
index bce2a4a0a..10816cd7a 100644
--- a/.github/workflows/full_clang_tidy.yaml
+++ b/.github/workflows/full_clang_tidy.yaml
@@ -14,7 +14,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
diff --git a/.github/workflows/package_memgraph.yaml b/.github/workflows/package_memgraph.yaml
index 48a61ca53..45a62f037 100644
--- a/.github/workflows/package_memgraph.yaml
+++ b/.github/workflows/package_memgraph.yaml
@@ -42,14 +42,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package amzn-2 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: amzn-2
           path: build/output/amzn-2/memgraph*.rpm
@@ -60,14 +60,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package centos-7 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: centos-7
           path: build/output/centos-7/memgraph*.rpm
@@ -78,14 +78,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package centos-9 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: centos-9
           path: build/output/centos-9/memgraph*.rpm
@@ -96,14 +96,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package debian-10 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: debian-10
           path: build/output/debian-10/memgraph*.deb
@@ -114,14 +114,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: debian-11
           path: build/output/debian-11/memgraph*.deb
@@ -132,14 +132,14 @@ jobs:
     timeout-minutes: 120
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package debian-11-arm ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: debian-11-aarch64
           path: build/output/debian-11-arm/memgraph*.deb
@@ -150,14 +150,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-platform
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: debian-11-platform
           path: build/output/debian-11/memgraph*.deb
@@ -168,7 +168,7 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
@@ -177,7 +177,7 @@ jobs:
           ./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker
           ./run.sh docker
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: docker
           path: build/output/docker/memgraph*.tar.gz
@@ -188,14 +188,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package fedora-36 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: fedora-36
           path: build/output/fedora-36/memgraph*.rpm
@@ -206,14 +206,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package ubuntu-18.04 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: ubuntu-18.04
           path: build/output/ubuntu-18.04/memgraph*.deb
@@ -224,14 +224,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package ubuntu-20.04 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: ubuntu-20.04
           path: build/output/ubuntu-20.04/memgraph*.deb
@@ -242,14 +242,14 @@ jobs:
     timeout-minutes: 60
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package ubuntu-22.04 ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: ubuntu-22.04
           path: build/output/ubuntu-22.04/memgraph*.deb
@@ -260,14 +260,14 @@ jobs:
     timeout-minutes: 120
     steps:
       - name: "Set up repository"
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0 # Required because of release/get_version.py
       - name: "Build package"
         run: |
           ./release/package/run.sh package ubuntu-22.04-arm ${{ github.event.inputs.build_type }}
       - name: "Upload package"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: ubuntu-22.04-aarch64
           path: build/output/ubuntu-22.04-arm/memgraph*.deb
@@ -279,7 +279,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Download artifacts
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           # name: # if name input parameter is not provided, all artifacts are downloaded
                   # and put in directories named after each one.
diff --git a/.github/workflows/performance_benchmarks.yaml b/.github/workflows/performance_benchmarks.yaml
index d31c661de..cc030c180 100644
--- a/.github/workflows/performance_benchmarks.yaml
+++ b/.github/workflows/performance_benchmarks.yaml
@@ -14,7 +14,7 @@ jobs:
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
diff --git a/.github/workflows/release_centos8.yaml b/.github/workflows/release_centos8.yaml
deleted file mode 100644
index c82916df6..000000000
--- a/.github/workflows/release_centos8.yaml
+++ /dev/null
@@ -1,318 +0,0 @@
-name: Release CentOS 8
-
-on:
-  workflow_dispatch:
-    inputs:
-      build_type:
-        type: choice
-        description: "Memgraph Build type. Default value is Release."
-        default: 'Release'
-        options:
-          - Release
-          - RelWithDebInfo
-
-  schedule:
-    - cron: "0 22 * * *"
-
-jobs:
-  community_build:
-    name: "Community build"
-    runs-on: [self-hosted, Linux, X64, CentOS8]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 960
-
-    steps:
-      - name: Set up repository
-        uses: actions/checkout@v3
-        with:
-          # Number of commits to fetch. `0` indicates all history for all
-          # branches and tags. (default: 1)
-          fetch-depth: 0
-
-      - name: Build community binaries
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Initialize dependencies.
-          ./init
-
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
-          # Build community binaries.
-          cd build
-          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
-          make -j$THREADS
-
-      - name: Run unit tests
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Run unit tests.
-          cd build
-          ctest -R memgraph__unit --output-on-failure
-
-  coverage_build:
-    name: "Coverage build"
-    runs-on: [self-hosted, Linux, X64, CentOS8]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-
-    steps:
-      - name: Set up repository
-        uses: actions/checkout@v3
-        with:
-          # Number of commits to fetch. `0` indicates all history for all
-          # branches and tags. (default: 1)
-          fetch-depth: 0
-
-      - name: Build coverage binaries
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Initialize dependencies.
-          ./init
-
-          # Build coverage binaries.
-          cd build
-          cmake -DTEST_COVERAGE=ON ..
-          make -j$THREADS memgraph__unit
-
-      - name: Run unit tests
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Run unit tests.
-          cd build
-          ctest -R memgraph__unit --output-on-failure
-
-      - name: Compute code coverage
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Compute code coverage.
-          cd tools/github
-          ./coverage_convert
-
-          # Package code coverage.
-          cd generated
-          tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
-
-      - name: Save code coverage
-        uses: actions/upload-artifact@v3
-        with:
-          name: "Code coverage"
-          path: tools/github/generated/code_coverage.tar.gz
-
-  debug_build:
-    name: "Debug build"
-    runs-on: [self-hosted, Linux, X64, CentOS8]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-
-    steps:
-      - name: Set up repository
-        uses: actions/checkout@v3
-        with:
-          # Number of commits to fetch. `0` indicates all history for all
-          # branches and tags. (default: 1)
-          fetch-depth: 0
-
-      - name: Build debug binaries
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Initialize dependencies.
-          ./init
-
-          # Build debug binaries.
-          cd build
-          cmake ..
-          make -j$THREADS
-
-      - name: Run leftover CTest tests
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Run leftover CTest tests (all except unit and benchmark tests).
-          cd build
-          ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
-
-      - name: Run drivers tests
-        run: |
-          ./tests/drivers/run.sh
-
-      - name: Run integration tests
-        run: |
-          tests/integration/run.sh
-
-      - name: Run cppcheck and clang-format
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Run cppcheck and clang-format.
-          cd tools/github
-          ./cppcheck_and_clang_format diff
-
-      - name: Save cppcheck and clang-format errors
-        uses: actions/upload-artifact@v3
-        with:
-          name: "Code coverage"
-          path: tools/github/cppcheck_and_clang_format.txt
-
-  release_build:
-    name: "Release build"
-    runs-on: [self-hosted, Linux, X64, CentOS8]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 960
-
-    steps:
-      - name: Set up repository
-        uses: actions/checkout@v3
-        with:
-          # Number of commits to fetch. `0` indicates all history for all
-          # branches and tags. (default: 1)
-          fetch-depth: 0
-
-      - name: Build release binaries
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Initialize dependencies.
-          ./init
-
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
-          # Build release binaries.
-          cd build
-          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
-          make -j$THREADS
-
-      - name: Create enterprise RPM package
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          cd build
-
-          # create mgconsole
-          # we use the -B to force the build
-          make -j$THREADS -B mgconsole
-
-          # Create enterprise RPM package.
-          mkdir output && cd output
-          cpack -G RPM --config ../CPackConfig.cmake
-          rpmlint memgraph*.rpm
-
-      - name: Save enterprise RPM package
-        uses: actions/upload-artifact@v3
-        with:
-          name: "Enterprise RPM package"
-          path: build/output/memgraph*.rpm
-
-      - name: Run micro benchmark tests
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Run micro benchmark tests.
-          cd build
-          # The `eval` benchmark needs a large stack limit.
-          ulimit -s 262144
-          ctest -R memgraph__benchmark -V
-
-      - name: Run macro benchmark tests
-        run: |
-          cd tests/macro_benchmark
-          ./harness QuerySuite MemgraphRunner \
-            --groups aggregation 1000_create unwind_create dense_expand match \
-            --no-strict
-
-      - name: Run parallel macro benchmark tests
-        run: |
-          cd tests/macro_benchmark
-          ./harness QueryParallelSuite MemgraphRunner \
-            --groups aggregation_parallel create_parallel bfs_parallel \
-            --num-database-workers 9 --num-clients-workers 30 \
-            --no-strict
-
-      - name: Run GQL Behave tests
-        run: |
-          cd tests
-          ./setup.sh /opt/toolchain-v4/activate
-          cd gql_behave
-          ./continuous_integration
-
-      - name: Save quality assurance status
-        uses: actions/upload-artifact@v3
-        with:
-          name: "GQL Behave Status"
-          path: |
-            tests/gql_behave/gql_behave_status.csv
-            tests/gql_behave/gql_behave_status.html
-
-      - name: Run unit tests
-        run: |
-          # Activate toolchain.
-          source /opt/toolchain-v4/activate
-
-          # Run unit tests.
-          cd build
-          ctest -R memgraph__unit --output-on-failure
-
-      - name: Run e2e tests
-        run: |
-          cd tests
-          ./setup.sh /opt/toolchain-v4/activate
-          source ve3/bin/activate_e2e
-          cd e2e
-          ./run.sh
-
-      - name: Run stress test (plain)
-        run: |
-          cd tests/stress
-          ./continuous_integration
-
-      - name: Run stress test (SSL)
-        run: |
-          cd tests/stress
-          ./continuous_integration --use-ssl
-
-      - name: Run stress test (large)
-        run: |
-          cd tests/stress
-          ./continuous_integration --large-dataset
-
-      - name: Run durability test (plain)
-        run: |
-          cd tests/stress
-          source ve3/bin/activate
-          python3 durability --num-steps 5
-
-      - name: Run durability test (large)
-        run: |
-          cd tests/stress
-          source ve3/bin/activate
-          python3 durability --num-steps 20
diff --git a/.github/workflows/release_debian10.yaml b/.github/workflows/release_debian10.yaml
index 9a38e4cfb..36b9148c3 100644
--- a/.github/workflows/release_debian10.yaml
+++ b/.github/workflows/release_debian10.yaml
@@ -14,19 +14,21 @@ on:
   schedule:
     - cron: "0 22 * * *"
 
+env:
+  THREADS: 24
+  MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
+  MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+  BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }}
+
 jobs:
   community_build:
     name: "Community build"
     runs-on: [self-hosted, Linux, X64, Debian10]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 960
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -40,10 +42,6 @@ jobs:
           # Initialize dependencies.
           ./init
 
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
           # Build community binaries.
           cd build
           cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
@@ -65,10 +63,11 @@ jobs:
       THREADS: 24
       MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
       MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -110,22 +109,19 @@ jobs:
           tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
 
       - name: Save code coverage
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: "Code coverage"
+          name: "Code coverage(Coverage build)"
           path: tools/github/generated/code_coverage.tar.gz
 
   debug_build:
     name: "Debug build"
     runs-on: [self-hosted, Linux, X64, Debian10]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -157,10 +153,6 @@ jobs:
         run: |
           ./tests/drivers/run.sh
 
-      - name: Run integration tests
-        run: |
-          tests/integration/run.sh
-
       - name: Run cppcheck and clang-format
         run: |
           # Activate toolchain.
@@ -171,23 +163,49 @@ jobs:
           ./cppcheck_and_clang_format diff
 
       - name: Save cppcheck and clang-format errors
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: "Code coverage"
+          name: "Code coverage(Debug build)"
           path: tools/github/cppcheck_and_clang_format.txt
 
-  release_build:
-    name: "Release build"
-    runs-on: [self-hosted, Linux, X64, Debian10, BigMemory]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 960
+  debug_integration_test:
+    name: "Debug integration tests"
+    runs-on: [self-hosted, Linux, X64, Debian10]
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build debug binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Initialize dependencies.
+          ./init
+
+          # Build debug binaries.
+          cd build
+          cmake ..
+          make -j$THREADS
+
+      - name: Run integration tests
+        run: |
+          tests/integration/run.sh
+
+  release_build:
+    name: "Release build"
+    runs-on: [self-hosted, Linux, X64, Debian10]
+    timeout-minutes: 90
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -201,10 +219,6 @@ jobs:
           # Initialize dependencies.
           ./init
 
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
           # Build release binaries.
           cd build
           cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
@@ -226,11 +240,60 @@ jobs:
           cpack -G DEB --config ../CPackConfig.cmake
 
       - name: Save enterprise DEB package
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "Enterprise DEB package"
           path: build/output/memgraph*.deb
 
+      - name: Run GQL Behave tests
+        run: |
+          cd tests
+          ./setup.sh /opt/toolchain-v4/activate
+          cd gql_behave
+          ./continuous_integration
+
+      - name: Save quality assurance status
+        uses: actions/upload-artifact@v4
+        with:
+          name: "GQL Behave Status"
+          path: |
+            tests/gql_behave/gql_behave_status.csv
+            tests/gql_behave/gql_behave_status.html
+
+      - name: Run unit tests
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Run unit tests.
+          cd build
+          ctest -R memgraph__unit --output-on-failure
+
+  release_benchmark_tests:
+    name: "Release Benchmark Tests"
+    runs-on: [self-hosted, Linux, X64, Debian10]
+    timeout-minutes: 90
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build release binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+          # Initialize dependencies.
+          ./init
+
+          # Build release binaries
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
       - name: Run micro benchmark tests
         run: |
           # Activate toolchain.
@@ -257,29 +320,38 @@ jobs:
             --num-database-workers 9 --num-clients-workers 30 \
             --no-strict
 
-      - name: Run GQL Behave tests
-        run: |
-          cd tests
-          ./setup.sh /opt/toolchain-v4/activate
-          cd gql_behave
-          ./continuous_integration
+  release_e2e_test:
+    if: false
+    name: "Release End-to-end Test"
+    runs-on: [self-hosted, Linux, X64, Debian10]
+    timeout-minutes: 90
 
-      - name: Save quality assurance status
-        uses: actions/upload-artifact@v3
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
         with:
-          name: "GQL Behave Status"
-          path: |
-            tests/gql_behave/gql_behave_status.csv
-            tests/gql_behave/gql_behave_status.html
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
 
-      - name: Run unit tests
+      - name: Build release binaries
         run: |
           # Activate toolchain.
           source /opt/toolchain-v4/activate
+          # Initialize dependencies.
+          ./init
 
-          # Run unit tests.
+          # Build release binaries
           cd build
-          ctest -R memgraph__unit --output-on-failure
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
+      - name: Ensure Kafka and Pulsar are up
+        run: |
+          cd tests/e2e/streams/kafka
+          docker-compose up -d
+          cd ../pulsar
+          docker-compose up -d
 
       - name: Run e2e tests
         run: |
@@ -289,6 +361,40 @@ jobs:
           cd e2e
           ./run.sh
 
+      - name: Ensure Kafka and Pulsar are down
+        if: always()
+        run: |
+          cd tests/e2e/streams/kafka
+          docker-compose down
+          cd ../pulsar
+          docker-compose down
+
+  release_durability_stress_tests:
+    name: "Release durability and stress tests"
+    runs-on: [self-hosted, Linux, X64, Debian10]
+    timeout-minutes: 90
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build release binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Initialize dependencies.
+          ./init
+
+          # Build release binaries.
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
       - name: Run stress test (plain)
         run: |
           cd tests/stress
@@ -299,11 +405,6 @@ jobs:
           cd tests/stress
           ./continuous_integration --use-ssl
 
-      - name: Run stress test (large)
-        run: |
-          cd tests/stress
-          ./continuous_integration --large-dataset
-
       - name: Run durability test (plain)
         run: |
           cd tests/stress
@@ -319,15 +420,11 @@ jobs:
   release_jepsen_test:
     name: "Release Jepsen Test"
     runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 60
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -340,22 +437,23 @@ jobs:
           # Initialize dependencies.
           ./init
 
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
           # Build only memgraph release binary.
           cd build
           cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
           make -j$THREADS memgraph
 
+      - name: Refresh Jepsen Cluster
+        run: |
+          cd tests/jepsen
+          ./run.sh cluster-refresh
+
       - name: Run Jepsen tests
         run: |
           cd tests/jepsen
           ./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
 
       - name: Save Jepsen report
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         if: ${{ always() }}
         with:
           name: "Jepsen Report"
diff --git a/.github/workflows/release_docker.yaml b/.github/workflows/release_docker.yaml
index 2ebb2e804..d5e02254b 100644
--- a/.github/workflows/release_docker.yaml
+++ b/.github/workflows/release_docker.yaml
@@ -19,7 +19,7 @@ jobs:
       DOCKER_REPOSITORY_NAME: memgraph
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v2
diff --git a/.github/workflows/release_mgbench_client.yaml b/.github/workflows/release_mgbench_client.yaml
index 2abdad2b9..88c65f7fe 100644
--- a/.github/workflows/release_mgbench_client.yaml
+++ b/.github/workflows/release_mgbench_client.yaml
@@ -20,7 +20,7 @@ jobs:
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v2
diff --git a/.github/workflows/release_ubuntu2004.yaml b/.github/workflows/release_ubuntu2004.yaml
index 225a4473c..96f85bdf3 100644
--- a/.github/workflows/release_ubuntu2004.yaml
+++ b/.github/workflows/release_ubuntu2004.yaml
@@ -14,19 +14,21 @@ on:
   schedule:
     - cron: "0 22 * * *"
 
+env:
+  THREADS: 24
+  MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
+  MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+  BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }}
+
 jobs:
   community_build:
     name: "Community build"
     runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 960
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -40,10 +42,6 @@ jobs:
           # Initialize dependencies.
           ./init
 
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
           # Build community binaries.
           cd build
           cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
@@ -61,14 +59,11 @@ jobs:
   coverage_build:
     name: "Coverage build"
     runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -110,22 +105,19 @@ jobs:
           tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
 
       - name: Save code coverage
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: "Code coverage"
+          name: "Code coverage(Coverage build)"
           path: tools/github/generated/code_coverage.tar.gz
 
   debug_build:
     name: "Debug build"
     runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -157,10 +149,6 @@ jobs:
         run: |
           ./tests/drivers/run.sh
 
-      - name: Run integration tests
-        run: |
-          tests/integration/run.sh
-
       - name: Run cppcheck and clang-format
         run: |
           # Activate toolchain.
@@ -171,23 +159,49 @@ jobs:
           ./cppcheck_and_clang_format diff
 
       - name: Save cppcheck and clang-format errors
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: "Code coverage"
+          name: "Code coverage(Debug build)"
           path: tools/github/cppcheck_and_clang_format.txt
 
+  debug_integration_test:
+    name: "Debug integration tests"
+    runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
+    timeout-minutes: 90
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build debug binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Initialize dependencies.
+          ./init
+
+          # Build debug binaries.
+          cd build
+          cmake ..
+          make -j$THREADS
+
+      - name: Run integration tests
+        run: |
+          tests/integration/run.sh
+
   release_build:
     name: "Release build"
     runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
-    env:
-      THREADS: 24
-      MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
-      MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
-    timeout-minutes: 960
+    timeout-minutes: 90
 
     steps:
       - name: Set up repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Number of commits to fetch. `0` indicates all history for all
           # branches and tags. (default: 1)
@@ -201,10 +215,6 @@ jobs:
           # Initialize dependencies.
           ./init
 
-          # Set default build_type to Release
-          INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
-          BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
-
           # Build release binaries.
           cd build
           cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
@@ -226,11 +236,60 @@ jobs:
           cpack -G DEB --config ../CPackConfig.cmake
 
       - name: Save enterprise DEB package
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: "Enterprise DEB package"
           path: build/output/memgraph*.deb
 
+      - name: Run GQL Behave tests
+        run: |
+          cd tests
+          ./setup.sh /opt/toolchain-v4/activate
+          cd gql_behave
+          ./continuous_integration
+
+      - name: Save quality assurance status
+        uses: actions/upload-artifact@v4
+        with:
+          name: "GQL Behave Status"
+          path: |
+            tests/gql_behave/gql_behave_status.csv
+            tests/gql_behave/gql_behave_status.html
+
+      - name: Run unit tests
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Run unit tests.
+          cd build
+          ctest -R memgraph__unit --output-on-failure
+
+  release_benchmark_tests:
+    name: "Release Benchmark Tests"
+    runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
+    timeout-minutes: 90
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build release binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+          # Initialize dependencies.
+          ./init
+
+          # Build release binaries
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
       - name: Run micro benchmark tests
         run: |
           # Activate toolchain.
@@ -257,29 +316,38 @@ jobs:
             --num-database-workers 9 --num-clients-workers 30 \
             --no-strict
 
-      - name: Run GQL Behave tests
-        run: |
-          cd tests
-          ./setup.sh /opt/toolchain-v4/activate
-          cd gql_behave
-          ./continuous_integration
+  release_e2e_test:
+    if: false
+    name: "Release End-to-end Test"
+    runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
+    timeout-minutes: 90
 
-      - name: Save quality assurance status
-        uses: actions/upload-artifact@v3
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
         with:
-          name: "GQL Behave Status"
-          path: |
-            tests/gql_behave/gql_behave_status.csv
-            tests/gql_behave/gql_behave_status.html
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
 
-      - name: Run unit tests
+      - name: Build release binaries
         run: |
           # Activate toolchain.
           source /opt/toolchain-v4/activate
+          # Initialize dependencies.
+          ./init
 
-          # Run unit tests.
+          # Build release binaries
           cd build
-          ctest -R memgraph__unit --output-on-failure
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
+      - name: Ensure Kafka and Pulsar are up
+        run: |
+          cd tests/e2e/streams/kafka
+          docker-compose up -d
+          cd ../pulsar
+          docker-compose up -d
 
       - name: Run e2e tests
         run: |
@@ -289,6 +357,40 @@ jobs:
           cd e2e
           ./run.sh
 
+      - name: Ensure Kafka and Pulsar are down
+        if: always()
+        run: |
+          cd tests/e2e/streams/kafka
+          docker-compose down
+          cd ../pulsar
+          docker-compose down
+
+  release_durability_stress_tests:
+    name: "Release durability and stress tests"
+    runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
+    timeout-minutes: 90
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build release binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Initialize dependencies.
+          ./init
+
+          # Build release binaries.
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
       - name: Run stress test (plain)
         run: |
           cd tests/stress
@@ -299,11 +401,6 @@ jobs:
           cd tests/stress
           ./continuous_integration --use-ssl
 
-      - name: Run stress test (large)
-        run: |
-          cd tests/stress
-          ./continuous_integration --large-dataset
-
       - name: Run durability test (plain)
         run: |
           cd tests/stress
diff --git a/.github/workflows/stress_test_large.yaml b/.github/workflows/stress_test_large.yaml
new file mode 100644
index 000000000..54a6c55ba
--- /dev/null
+++ b/.github/workflows/stress_test_large.yaml
@@ -0,0 +1,62 @@
+name: Stress test large
+
+on:
+  workflow_dispatch:
+    inputs:
+      build_type:
+        type: choice
+        description: "Memgraph Build type. Default value is Release."
+        default: 'Release'
+        options:
+          - Release
+          - RelWithDebInfo
+
+  schedule:
+    - cron: "0 22 * * *"
+
+env:
+  THREADS: 24
+  MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
+  MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
+  BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }}
+
+jobs:
+  stress_test_large:
+    name: "Stress test large"
+    timeout-minutes: 600
+    strategy:
+      matrix:
+        os: [Debian10, Ubuntu20.04]
+        extra: [BigMemory, Gen8]
+        exclude:
+          - os: Debian10
+            extra: Gen8
+          - os: Ubuntu20.04
+            extra: BigMemory
+    runs-on: [self-hosted, Linux, X64, "${{ matrix.os }}", "${{ matrix.extra }}"]
+
+    steps:
+      - name: Set up repository
+        uses: actions/checkout@v4
+        with:
+          # Number of commits to fetch. `0` indicates all history for all
+          # branches and tags. (default: 1)
+          fetch-depth: 0
+
+      - name: Build release binaries
+        run: |
+          # Activate toolchain.
+          source /opt/toolchain-v4/activate
+
+          # Initialize dependencies.
+          ./init
+
+          # Build release binaries.
+          cd build
+          cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+          make -j$THREADS
+
+      - name: Run stress test (large)
+        run: |
+          cd tests/stress
+          ./continuous_integration --large-dataset
diff --git a/.github/workflows/upload_to_s3.yaml b/.github/workflows/upload_to_s3.yaml
index 978af7d66..b6bda4ca5 100644
--- a/.github/workflows/upload_to_s3.yaml
+++ b/.github/workflows/upload_to_s3.yaml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Download artifacts
-        uses: dawidd6/action-download-artifact@v2
+        uses: dawidd6/action-download-artifact@v4
         with:
           workflow: package_all.yaml
           workflow_conclusion: success
diff --git a/ADRs/001_tantivy.md b/ADRs/001_tantivy.md
new file mode 100644
index 000000000..ac887c861
--- /dev/null
+++ b/ADRs/001_tantivy.md
@@ -0,0 +1,32 @@
+# Tantivy ADR
+
+**Author**
+Marko Budiselic (github.com/gitbuda)
+
+**Status**
+APPROVED
+
+**Date**
+January 5, 2024
+
+**Problem**
+
+For some of Memgraph workloads, text search is a required feature. We don't
+want to build a new text search engine because that's not Memgraph's core
+value.
+
+**Criteria**
+
+- easy integration with our C++ codebase
+- ability to operate in-memory and on-disk
+- sufficient features (regex, full-text search, fuzzy search, aggregations over
+  text data)
+- production-ready
+
+**Decision**
+
+All known C++ libraries are not production-ready. Recent Rust libraries, in
+particular [Tantivy](https://github.com/quickwit-oss/tantivy), seem to provide
+much more features, it is production ready. The way how we'll integrate Tantivy
+into the current Memgraph codebase is via
+[cxx](https://github.com/dtolnay/cxx). **We select Tantivy.**
diff --git a/ADRs/002_nuraft.md b/ADRs/002_nuraft.md
new file mode 100644
index 000000000..9be239e17
--- /dev/null
+++ b/ADRs/002_nuraft.md
@@ -0,0 +1,34 @@
+# NuRaft ADR
+
+**Author**
+Marko Budiselic (github.com/gitbuda)
+
+**Status**
+PROPOSED
+
+**Date**
+January 10, 2024
+
+**Problem**
+
+In order to enhance Memgraph to have High Availability features as requested by
+customers, we want to have reliable coordinators backed by RAFT consensus algorithm. Implementing
+RAFT to be correct and performant is a very challenging task. Skillful Memgraph
+engineers already tried 3 times and failed to deliver in a reasonable timeframe
+all three times (approximately 4 person-weeks of engineering work each time).
+
+**Criteria**
+
+- easy integration with our C++ codebase
+- heavily tested in production environments
+- implementation of performance optimizations on top of the canonical Raft
+  implementation
+
+**Decision**
+
+There are a few, robust C++ implementations of Raft but as a part of other
+projects or bigger libraries. **We select
+[NuRaft](https://github.com/eBay/NuRaft)** because it focuses on delivering
+Raft without bloatware, and it's used by
+[Clickhouse](https://github.com/ClickHouse/ClickHouse) (an comparable peer to
+Memgraph, a very well-established product).
diff --git a/ADRs/README.md b/ADRs/README.md
new file mode 100644
index 000000000..74b300037
--- /dev/null
+++ b/ADRs/README.md
@@ -0,0 +1,67 @@
+# Architecture Decision Records
+
+Also known as ADRs. This practice has become widespread in many
+high performing engineering teams. It is a technique for communicating
+between software engineers. ADRs provide a clear and documented
+history of architectural choices, ensuring that everyone on the
+team is on the same page. This improves communication and reduces
+misunderstandings. The act of recording decisions encourages
+thoughtful consideration before making choices. This can lead to
+more robust and better-informed architectural decisions.
+
+Links must be created, pointing both to and from the Github Issues
+and/or the Notion Program Management "Initiative" database.
+
+ADRs are complimentary to any tech specs that get written while
+designing a solution. ADRs are very short and to the point, while
+tech specs will include diagrams and can be quite verbose.
+
+## HOWTO
+
+Each ADR will be assigned a monotonically increasing unique numeric
+identifier, which will be zero-padded to 3 digits. Each ADR will
+be in a single markdown file containing no more than one page of
+text, and the filename will start with that unique identifier,
+followed by a camel case phrase summarizing the problem.  For
+example: `001_architecture_decision_records.md` or
+`002_big_integration_cap_theorem.md`.
+
+We want to use an ADR when:
+1. Significant Impact: This includes choices that affect scalability, performance, or fundamental design principles.
+1. Long-Term Ramifications: When a decision is expected to have long-term ramifications or is difficult to reverse.
+1. Architectural Principles: ADRs are suitable for documenting decisions related to architectural principles, frameworks, or patterns that shape the system's structure.
+1. Controversial Choices: When a decision is likely to be controversial or may require justification in the future.
+
+The most senior engineer on a project will evaluate and decide
+whether or not an ADR is needed.
+
+## Do
+
+1. Keep them brief and concise.
+1. Explain the trade-offs.
+1. Each ADR should be about one AD, not multiple ADs
+1. Don't alter existing information in an ADR. Instead, amend the ADR by adding new information, or supersede the ADR by creating a new ADR.
+1. Explain your organization's situation and business priorities.
+1. Include rationale and considerations based on social and skills makeups of your teams.
+1. Include pros and cons that are relevant, and describe them in terms that align with your needs and goals.
+1. Explain what follows from making the decision. This can include the effects, outcomes, outputs, follow ups, and more.
+
+## Don't
+
+1. Try to guess what the executive leader wants, and then attempt to please them. Be objective.
+1. Try to solve everything all at once. A pretty good solution now is MUCH BETTER than a perfect solution later. Carpe diem!
+1. Hide any doubts or unanswered questions.
+1. Make it a sales pitch. Everything has upsides and downsides - be authentic and honest about them.
+1. Perform merely a superficial investigation. If an ADR doesn't call for some deep thinking, then it probably shouldn't exist.
+1. Ignore the long-term costs such as performance, tech debt or hardware and maintenance.
+1. Get tunnel vision where creative or surprising approaches are not explored.
+
+# Template - use the format below for each new ADR
+
+1. **Author** - who has written the ADR
+1. **Status** - one of: PROPOSED, ACCEPTED, REJECTED, SUPERSEDED-BY or DEPRECATED
+1. **Date** - when the status was most recently updated
+1. **Problem** - a concise paragraph explaining the context
+1. **Criteria** - a list of the two or three metrics by which the solution was evaluated, and their relative weights (importance)
+1. **Decision** - what was chosen as the way forward, and what the consequences are of the decision
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a5ad2612a..266a3bedb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -271,6 +271,17 @@ endif()
 set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
 add_subdirectory(libs EXCLUDE_FROM_ALL)
 
+option(MG_EXPERIMENTAL_HIGH_AVAILABILITY "Feature flag for experimental high availability" OFF)
+
+if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_HIGH_AVAILABILITY)
+    set(MG_EXPERIMENTAL_HIGH_AVAILABILITY OFF)
+    message(FATAL_ERROR "MG_EXPERIMENTAL_HIGH_AVAILABILITY must be used with enterpise version of the code.")
+endif ()
+
+if (MG_EXPERIMENTAL_HIGH_AVAILABILITY)
+    add_compile_definitions(MG_EXPERIMENTAL_HIGH_AVAILABILITY)
+endif ()
+
 # Optional subproject configuration -------------------------------------------
 option(TEST_COVERAGE "Generate coverage reports from running memgraph" OFF)
 option(TOOLS "Build tools binaries" ON)
@@ -279,6 +290,18 @@ option(ASAN "Build with Address Sanitizer. To get a reasonable performance optio
 option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF)
 option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF)
 
+# Build feature flags
+option(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY "Feature flag for experimental replicaition of multitenacy" OFF)
+
+if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
+    set(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY OFF)
+    message(FATAL_ERROR "MG_EXPERIMENTAL_REPLICATION_MULTITENANCY with community edition build isn't possible")
+endif ()
+
+if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
+    add_compile_definitions(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
+endif ()
+
 if (TEST_COVERAGE)
   string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
   if (NOT lower_build_type STREQUAL "debug")
diff --git a/include/mg_exceptions.hpp b/include/mg_exceptions.hpp
index 53a338b86..d526d4e43 100644
--- a/include/mg_exceptions.hpp
+++ b/include/mg_exceptions.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -25,7 +25,7 @@ namespace mg_exception {
 template <typename FirstArg, typename... Args>
 std::string StringSerialize(FirstArg &&firstArg, Args &&...args) {
   std::stringstream stream;
-  stream << firstArg;
+  stream << std::forward<FirstArg>(firstArg);
   ((stream << " " << args), ...);
   return stream.str();
 }
diff --git a/libs/setup.sh b/libs/setup.sh
index 028797679..2f968f71c 100755
--- a/libs/setup.sh
+++ b/libs/setup.sh
@@ -259,7 +259,7 @@ repo_clone_try_double "${primary_urls[absl]}" "${secondary_urls[absl]}" "absl" "
 
 # jemalloc ea6b3e973b477b8061e0076bb257dbd7f3faa756
 JEMALLOC_COMMIT_VERSION="5.2.1"
-repo_clone_try_double "${secondary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION"
+repo_clone_try_double "${primary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION"
 
 # this is hack for cmake in libs to set path, and for FindJemalloc to use Jemalloc_INCLUDE_DIR
 pushd jemalloc
diff --git a/licenses/BSL.txt b/licenses/BSL.txt
index 9cca9dccf..c41a08af1 100644
--- a/licenses/BSL.txt
+++ b/licenses/BSL.txt
@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT:   You may use the Licensed Work in accordance with the
                         3. using the Licensed Work to create a work or solution
                         which competes (or might reasonably be expected to
                         compete) with the Licensed Work.
-CHANGE DATE:            2027-08-12
+CHANGE DATE:            2028-21-01
 CHANGE LICENSE:         Apache License, Version 2.0
 
 For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ba8784b19..4685da727 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -22,6 +22,8 @@ add_subdirectory(dbms)
 add_subdirectory(flags)
 add_subdirectory(distributed)
 add_subdirectory(replication)
+add_subdirectory(coordination)
+add_subdirectory(replication_coordination_glue)
 
 string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
 
@@ -53,7 +55,10 @@ set_target_properties(memgraph PROPERTIES
         OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
 
         # Output the executable in main binary dir.
-        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
+
+        POSITION_INDEPENDENT_CODE ON
+        )
 
 # Create symlink to the built executable.
 add_custom_command(TARGET memgraph POST_BUILD
diff --git a/src/auth/auth.cpp b/src/auth/auth.cpp
index cfe9dbdbe..88f0c4410 100644
--- a/src/auth/auth.cpp
+++ b/src/auth/auth.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
 // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -8,17 +8,15 @@
 
 #include "auth/auth.hpp"
 
-#include <cstring>
 #include <iostream>
-#include <limits>
 #include <utility>
 
 #include <fmt/format.h>
 
+#include "auth/crypto.hpp"
 #include "auth/exceptions.hpp"
 #include "license/license.hpp"
 #include "utils/flag_validation.hpp"
-#include "utils/logging.hpp"
 #include "utils/message.hpp"
 #include "utils/settings.hpp"
 #include "utils/string.hpp"
@@ -46,6 +44,9 @@ namespace memgraph::auth {
 const std::string kUserPrefix = "user:";
 const std::string kRolePrefix = "role:";
 const std::string kLinkPrefix = "link:";
+const std::string kVersion = "version";
+
+static constexpr auto kVersionV1 = "V1";
 
 /**
  * All data stored in the `Auth` storage is stored in an underlying
@@ -64,7 +65,59 @@ const std::string kLinkPrefix = "link:";
  * key="link:<username>", value="<rolename>"
  */
 
-Auth::Auth(const std::string &storage_directory) : storage_(storage_directory), module_(FLAGS_auth_module_executable) {}
+namespace {
+void MigrateVersions(kvstore::KVStore &store) {
+  static constexpr auto kPasswordHashV0V1 = "password_hash";
+  auto version_str = store.Get(kVersion);
+
+  if (!version_str) {
+    using namespace std::string_literals;
+
+    // pre versioning, add version to the store
+    auto puts = std::map<std::string, std::string>{{kVersion, kVersionV1}};
+
+    // also add hash kind into durability
+
+    auto it = store.begin(kUserPrefix);
+    auto const e = store.end(kUserPrefix);
+
+    if (it != e) {
+      const auto hash_algo = CurrentHashAlgorithm();
+      spdlog::info("Updating auth durability, assuming previously stored as {}", AsString(hash_algo));
+
+      for (; it != e; ++it) {
+        auto const &[key, value] = *it;
+        try {
+          auto user_data = nlohmann::json::parse(value);
+
+          auto password_hash = user_data[kPasswordHashV0V1];
+          if (!password_hash.is_string()) {
+            throw AuthException("Couldn't load user data!");
+          }
+          // upgrade the password_hash to include the hash algortihm
+          if (password_hash.empty()) {
+            user_data[kPasswordHashV0V1] = nullptr;
+          } else {
+            user_data[kPasswordHashV0V1] = HashedPassword{hash_algo, password_hash};
+          }
+          puts.emplace(key, user_data.dump());
+        } catch (const nlohmann::json::parse_error &e) {
+          throw AuthException("Couldn't load user data!");
+        }
+      }
+    }
+
+    // Perform migration to V1
+    store.PutMultiple(puts);
+    version_str = kVersionV1;
+  }
+}
+};  // namespace
+
+Auth::Auth(std::string storage_directory, Config config)
+    : storage_(std::move(storage_directory)), module_(FLAGS_auth_module_executable), config_{std::move(config)} {
+  MigrateVersions(storage_);
+}
 
 std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
   if (module_.IsUsed()) {
@@ -113,7 +166,7 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
         return std::nullopt;
       }
     } else {
-      user->UpdatePassword(password);
+      UpdatePassword(*user, password);
     }
     if (FLAGS_auth_module_manage_roles) {
       if (!rolename.empty()) {
@@ -155,6 +208,10 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
                                           username, "https://memgr.ph/auth"));
       return std::nullopt;
     }
+    if (user->UpgradeHash(password)) {
+      SaveUser(*user);
+    }
+
     return user;
   }
 }
@@ -197,13 +254,46 @@ void Auth::SaveUser(const User &user) {
   }
 }
 
+void Auth::UpdatePassword(auth::User &user, const std::optional<std::string> &password) {
+  // Check if null
+  if (!password) {
+    if (!config_.password_permit_null) {
+      throw AuthException("Null passwords aren't permitted!");
+    }
+  } else {
+    // Check if compliant with our filter
+    if (config_.custom_password_regex) {
+      if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings);
+          license_check_result.HasError()) {
+        throw AuthException(
+            "Custom password regex is a Memgraph Enterprise feature. Please set the config "
+            "(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
+            glue::kDefaultPasswordRegex,
+            license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
+      }
+    }
+    if (!std::regex_match(*password, config_.password_regex)) {
+      throw AuthException(
+          "The user password doesn't conform to the required strength! Regex: "
+          "\"{}\"",
+          config_.password_regex_str);
+    }
+  }
+
+  // All checks passed; update
+  user.UpdatePassword(password);
+}
+
 std::optional<User> Auth::AddUser(const std::string &username, const std::optional<std::string> &password) {
+  if (!NameRegexMatch(username)) {
+    throw AuthException("Invalid user name.");
+  }
   auto existing_user = GetUser(username);
   if (existing_user) return std::nullopt;
   auto existing_role = GetRole(username);
   if (existing_role) return std::nullopt;
   auto new_user = User(username);
-  new_user.UpdatePassword(password);
+  UpdatePassword(new_user, password);
   SaveUser(new_user);
   return new_user;
 }
@@ -255,10 +345,11 @@ void Auth::SaveRole(const Role &role) {
 }
 
 std::optional<Role> Auth::AddRole(const std::string &rolename) {
-  auto existing_role = GetRole(rolename);
-  if (existing_role) return std::nullopt;
-  auto existing_user = GetUser(rolename);
-  if (existing_user) return std::nullopt;
+  if (!NameRegexMatch(rolename)) {
+    throw AuthException("Invalid role name.");
+  }
+  if (auto existing_role = GetRole(rolename)) return std::nullopt;
+  if (auto existing_user = GetUser(rolename)) return std::nullopt;
   auto new_role = Role(rolename);
   SaveRole(new_role);
   return new_role;
@@ -285,8 +376,7 @@ std::vector<auth::Role> Auth::AllRoles() const {
   for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix); ++it) {
     auto rolename = it->first.substr(kRolePrefix.size());
     if (rolename != utils::ToLowerCase(rolename)) continue;
-    auto role = GetRole(rolename);
-    if (role) {
+    if (auto role = GetRole(rolename)) {
       ret.push_back(*role);
     } else {
       throw AuthException("Couldn't load role '{}'!", rolename);
@@ -296,15 +386,14 @@ std::vector<auth::Role> Auth::AllRoles() const {
 }
 
 std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) const {
-  auto rolename = utils::ToLowerCase(rolename_orig);
+  const auto rolename = utils::ToLowerCase(rolename_orig);
   std::vector<auth::User> ret;
   for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix); ++it) {
     auto username = it->first.substr(kLinkPrefix.size());
     if (username != utils::ToLowerCase(username)) continue;
     if (it->second != utils::ToLowerCase(it->second)) continue;
     if (it->second == rolename) {
-      auto user = GetUser(username);
-      if (user) {
+      if (auto user = GetUser(username)) {
         ret.push_back(std::move(*user));
       } else {
         throw AuthException("Couldn't load user '{}'!", username);
@@ -316,8 +405,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
 
 #ifdef MG_ENTERPRISE
 bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
-  auto user = GetUser(name);
-  if (user) {
+  if (auto user = GetUser(name)) {
     if (db == kAllDatabases) {
       user->db_access().GrantAll();
     } else {
@@ -330,8 +418,7 @@ bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
 }
 
 bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) {
-  auto user = GetUser(name);
-  if (user) {
+  if (auto user = GetUser(name)) {
     if (db == kAllDatabases) {
       user->db_access().DenyAll();
     } else {
@@ -346,17 +433,15 @@ bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name
 void Auth::DeleteDatabase(const std::string &db) {
   for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
     auto username = it->first.substr(kUserPrefix.size());
-    auto user = GetUser(username);
-    if (user) {
+    if (auto user = GetUser(username)) {
       user->db_access().Delete(db);
       SaveUser(*user);
     }
   }
 }
 
-bool Auth::SetMainDatabase(const std::string &db, const std::string &name) {
-  auto user = GetUser(name);
-  if (user) {
+bool Auth::SetMainDatabase(std::string_view db, const std::string &name) {
+  if (auto user = GetUser(name)) {
     if (!user->db_access().SetDefault(db)) {
       throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name);
     }
@@ -367,4 +452,19 @@ bool Auth::SetMainDatabase(const std::string &db, const std::string &name) {
 }
 #endif
 
+bool Auth::NameRegexMatch(const std::string &user_or_role) const {
+  if (config_.custom_name_regex) {
+    if (const auto license_check_result =
+            memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings);
+        license_check_result.HasError()) {
+      throw memgraph::auth::AuthException(
+          "Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
+          "(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
+          glue::kDefaultUserRoleRegex,
+          memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
+    }
+  }
+  return std::regex_match(user_or_role, config_.name_regex);
+}
+
 }  // namespace memgraph::auth
diff --git a/src/auth/auth.hpp b/src/auth/auth.hpp
index 8d2a9d91c..aa90c349a 100644
--- a/src/auth/auth.hpp
+++ b/src/auth/auth.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
 // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -10,11 +10,13 @@
 
 #include <mutex>
 #include <optional>
+#include <regex>
 #include <vector>
 
 #include "auth/exceptions.hpp"
 #include "auth/models.hpp"
 #include "auth/module.hpp"
+#include "glue/auth_global.hpp"
 #include "kvstore/kvstore.hpp"
 #include "utils/settings.hpp"
 
@@ -31,7 +33,40 @@ static const constexpr char *const kAllDatabases = "*";
  */
 class Auth final {
  public:
-  explicit Auth(const std::string &storage_directory);
+  struct Config {
+    Config() {}
+    Config(std::string name_regex, std::string password_regex, bool password_permit_null)
+        : name_regex_str{std::move(name_regex)},
+          password_regex_str{std::move(password_regex)},
+          password_permit_null{password_permit_null},
+          custom_name_regex{name_regex_str != glue::kDefaultUserRoleRegex},
+          name_regex{name_regex_str},
+          custom_password_regex{password_regex_str != glue::kDefaultPasswordRegex},
+          password_regex{password_regex_str} {}
+
+    std::string name_regex_str{glue::kDefaultUserRoleRegex};
+    std::string password_regex_str{glue::kDefaultPasswordRegex};
+    bool password_permit_null{true};
+
+   private:
+    friend class Auth;
+    bool custom_name_regex{false};
+    std::regex name_regex{name_regex_str};
+    bool custom_password_regex{false};
+    std::regex password_regex{password_regex_str};
+  };
+
+  explicit Auth(std::string storage_directory, Config config);
+
+  /**
+   * @brief Set the Config object
+   *
+   * @param config
+   */
+  void SetConfig(Config config) {
+    // NOTE: The Auth class itself is not thread-safe, higher-level code needs to synchronize it when using it.
+    config_ = std::move(config);
+  }
 
   /**
    * Authenticates a user using his username and password.
@@ -85,6 +120,14 @@ class Auth final {
    */
   bool RemoveUser(const std::string &username);
 
+  /**
+   * @brief
+   *
+   * @param user
+   * @param password
+   */
+  void UpdatePassword(auth::User &user, const std::optional<std::string> &password);
+
   /**
    * Gets all users from the storage.
    *
@@ -195,14 +238,24 @@ class Auth final {
    * @return true on success
    * @throw AuthException if unable to find or update the user
    */
-  bool SetMainDatabase(const std::string &db, const std::string &name);
+  bool SetMainDatabase(std::string_view db, const std::string &name);
 #endif
 
  private:
+  /**
+   * @brief
+   *
+   * @param user_or_role
+   * @return true
+   * @return false
+   */
+  bool NameRegexMatch(const std::string &user_or_role) const;
+
   // Even though the `kvstore::KVStore` class is guaranteed to be thread-safe,
   // Auth is not thread-safe because modifying users and roles might require
   // more than one operation on the storage.
   kvstore::KVStore storage_;
   auth::Module module_;
+  Config config_;
 };
 }  // namespace memgraph::auth
diff --git a/src/auth/crypto.cpp b/src/auth/crypto.cpp
index c433eaf62..a8351635a 100644
--- a/src/auth/crypto.cpp
+++ b/src/auth/crypto.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
 // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -22,10 +22,14 @@
 
 namespace {
 using namespace std::literals;
-inline constexpr std::array password_encryption_mappings{
-    std::pair{"bcrypt"sv, memgraph::auth::PasswordEncryptionAlgorithm::BCRYPT},
-    std::pair{"sha256"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256},
-    std::pair{"sha256-multiple"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256_MULTIPLE}};
+
+constexpr auto kHashAlgo = "hash_algo";
+constexpr auto kPasswordHash = "password_hash";
+
+inline constexpr std::array password_hash_mappings{
+    std::pair{"bcrypt"sv, memgraph::auth::PasswordHashAlgorithm::BCRYPT},
+    std::pair{"sha256"sv, memgraph::auth::PasswordHashAlgorithm::SHA256},
+    std::pair{"sha256-multiple"sv, memgraph::auth::PasswordHashAlgorithm::SHA256_MULTIPLE}};
 
 inline constexpr uint64_t ONE_SHA_ITERATION = 1;
 inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
@@ -35,7 +39,7 @@ inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
 DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
                         "The password encryption algorithm used for authentication.", {
                           if (const auto result =
-                                  memgraph::utils::IsValidEnumValueString(value, password_encryption_mappings);
+                                  memgraph::utils::IsValidEnumValueString(value, password_hash_mappings);
                               result.HasError()) {
                             const auto error = result.GetError();
                             switch (error) {
@@ -45,7 +49,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
                               }
                               case memgraph::utils::ValidationError::InvalidValue: {
                                 std::cout << "Invalid value for password encryption algorithm. Allowed values: "
-                                          << memgraph::utils::GetAllowedEnumValuesString(password_encryption_mappings)
+                                          << memgraph::utils::GetAllowedEnumValuesString(password_hash_mappings)
                                           << std::endl;
                                 break;
                               }
@@ -58,7 +62,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
 
 namespace memgraph::auth {
 namespace BCrypt {
-std::string EncryptPassword(const std::string &password) {
+std::string HashPassword(const std::string &password) {
   char salt[BCRYPT_HASHSIZE];
   char hash[BCRYPT_HASHSIZE];
 
@@ -86,16 +90,30 @@ bool VerifyPassword(const std::string &password, const std::string &hash) {
 }  // namespace BCrypt
 
 namespace SHA {
+
+namespace {
+
+constexpr auto SHA_LENGTH = 64U;
+constexpr auto SALT_SIZE = 16U;
+constexpr auto SALT_SIZE_DURABLE = SALT_SIZE * 2;
+
 #if OPENSSL_VERSION_MAJOR >= 3
-std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t number_of_iterations) {
+std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t number_of_iterations,
+                                 std::string_view salt) {
   unsigned char hash[SHA256_DIGEST_LENGTH];
 
   EVP_MD_CTX *ctx = EVP_MD_CTX_new();
   EVP_MD *md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr);
 
   EVP_DigestInit_ex(ctx, md, nullptr);
+
+  if (!salt.empty()) {
+    DMG_ASSERT(salt.size() == SALT_SIZE);
+    EVP_DigestUpdate(ctx, salt.data(), salt.size());
+  }
+
   for (auto i = 0; i < number_of_iterations; i++) {
-    EVP_DigestUpdate(ctx, password.c_str(), password.size());
+    EVP_DigestUpdate(ctx, password.data(), password.size());
   }
   EVP_DigestFinal_ex(ctx, hash, nullptr);
 
@@ -103,6 +121,11 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t
   EVP_MD_CTX_free(ctx);
 
   std::stringstream result_stream;
+
+  for (unsigned char salt_char : salt) {
+    result_stream << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU);
+  }
+
   for (auto hash_char : hash) {
     result_stream << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
   }
@@ -110,17 +133,27 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t
   return result_stream.str();
 }
 #else
-std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_t number_of_iterations) {
+std::string HashPasswordOpenSSL1_1(std::string_view password, const uint64_t number_of_iterations,
+                                   std::string_view salt) {
   unsigned char hash[SHA256_DIGEST_LENGTH];
 
   SHA256_CTX sha256;
   SHA256_Init(&sha256);
+
+  if (!salt.empty()) {
+    DMG_ASSERT(salt.size() == SALT_SIZE);
+    SHA256_Update(&sha256, salt.data(), salt.size());
+  }
+
   for (auto i = 0; i < number_of_iterations; i++) {
-    SHA256_Update(&sha256, password.c_str(), password.size());
+    SHA256_Update(&sha256, password.data(), password.size());
   }
   SHA256_Final(hash, &sha256);
 
   std::stringstream ss;
+  for (unsigned char salt_char : salt) {
+    ss << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU);
+  }
   for (auto hash_char : hash) {
     ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
   }
@@ -129,55 +162,144 @@ std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_
 }
 #endif
 
-std::string EncryptPassword(const std::string &password, const uint64_t number_of_iterations) {
+std::string HashPassword(std::string_view password, const uint64_t number_of_iterations, std::string_view salt) {
 #if OPENSSL_VERSION_MAJOR >= 3
-  return EncryptPasswordOpenSSL3(password, number_of_iterations);
+  return HashPasswordOpenSSL3(password, number_of_iterations, salt);
 #else
-  return EncryptPasswordOpenSSL1_1(password, number_of_iterations);
+  return HashPasswordOpenSSL1_1(password, number_of_iterations, salt);
 #endif
 }
 
-bool VerifyPassword(const std::string &password, const std::string &hash, const uint64_t number_of_iterations) {
-  auto password_hash = EncryptPassword(password, number_of_iterations);
+auto ExtractSalt(std::string_view salt_durable) -> std::array<char, SALT_SIZE> {
+  static_assert(SALT_SIZE_DURABLE % 2 == 0);
+  static_assert(SALT_SIZE_DURABLE / 2 == SALT_SIZE);
+
+  MG_ASSERT(salt_durable.size() == SALT_SIZE_DURABLE);
+  auto const *b = salt_durable.cbegin();
+  auto const *const e = salt_durable.cend();
+
+  auto salt = std::array<char, SALT_SIZE>{};
+  auto *inserter = salt.begin();
+
+  auto const toval = [](char a) -> uint8_t {
+    if ('0' <= a && a <= '9') {
+      return a - '0';
+    }
+    if ('a' <= a && a <= 'f') {
+      return 10 + (a - 'a');
+    }
+    MG_ASSERT(false, "Currupt hash, can't extract salt");
+    __builtin_unreachable();
+  };
+
+  for (; b != e; b += 2, ++inserter) {
+    *inserter = static_cast<char>(static_cast<uint8_t>(toval(b[0]) << 4U) | toval(b[1]));
+  }
+  return salt;
+}
+
+bool IsSalted(std::string_view hash) { return hash.size() == SHA_LENGTH + SALT_SIZE_DURABLE; }
+
+bool VerifyPassword(std::string_view password, std::string_view hash, const uint64_t number_of_iterations) {
+  auto password_hash = std::invoke([&] {
+    if (hash.size() == SHA_LENGTH) [[unlikely]] {
+      // Just SHA256
+      return HashPassword(password, number_of_iterations, {});
+    } else {
+      // SHA256 + SALT
+      MG_ASSERT(IsSalted(hash));
+      auto const salt_durable = std::string_view{hash.data(), SALT_SIZE_DURABLE};
+      std::array<char, SALT_SIZE> salt = ExtractSalt(salt_durable);
+      return HashPassword(password, number_of_iterations, {salt.data(), salt.size()});
+    }
+  });
   return password_hash == hash;
 }
+
+}  // namespace
+
 }  // namespace SHA
 
-bool VerifyPassword(const std::string &password, const std::string &hash) {
-  const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>(
-      FLAGS_password_encryption_algorithm, password_encryption_mappings);
+HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo) {
+  auto const hash_algo = override_algo.value_or(CurrentHashAlgorithm());
+  auto password_hash = std::invoke([&] {
+    switch (hash_algo) {
+      case PasswordHashAlgorithm::BCRYPT: {
+        return BCrypt::HashPassword(password);
+      }
+      case PasswordHashAlgorithm::SHA256:
+      case PasswordHashAlgorithm::SHA256_MULTIPLE: {
+        auto gen = std::mt19937(std::random_device{}());
+        auto salt = std::array<char, SHA::SALT_SIZE>{};
+        auto dis = std::uniform_int_distribution<unsigned char>(0, 255);
+        std::generate(salt.begin(), salt.end(), [&]() { return dis(gen); });
+        auto iterations = (hash_algo == PasswordHashAlgorithm::SHA256) ? ONE_SHA_ITERATION : MULTIPLE_SHA_ITERATIONS;
+        return SHA::HashPassword(password, iterations, {salt.data(), salt.size()});
+      }
+    }
+  });
+  return HashedPassword{hash_algo, std::move(password_hash)};
+};
 
-  if (!password_encryption_algorithm.has_value()) {
-    throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
+namespace {
+
+auto InternalParseHashAlgorithm(std::string_view algo) -> PasswordHashAlgorithm {
+  auto maybe_parsed = utils::StringToEnum<PasswordHashAlgorithm>(algo, password_hash_mappings);
+  if (!maybe_parsed) {
+    throw AuthException("Invalid password encryption '{}'!", algo);
   }
-
-  switch (password_encryption_algorithm.value()) {
-    case PasswordEncryptionAlgorithm::BCRYPT:
-      return BCrypt::VerifyPassword(password, hash);
-    case PasswordEncryptionAlgorithm::SHA256:
-      return SHA::VerifyPassword(password, hash, ONE_SHA_ITERATION);
-    case PasswordEncryptionAlgorithm::SHA256_MULTIPLE:
-      return SHA::VerifyPassword(password, hash, MULTIPLE_SHA_ITERATIONS);
-  }
-
-  throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
+  return *maybe_parsed;
 }
 
-std::string EncryptPassword(const std::string &password) {
-  const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>(
-      FLAGS_password_encryption_algorithm, password_encryption_mappings);
+PasswordHashAlgorithm &InternalCurrentHashAlgorithm() {
+  static auto current = PasswordHashAlgorithm::BCRYPT;
+  static std::once_flag flag;
+  std::call_once(flag, [] { current = InternalParseHashAlgorithm(FLAGS_password_encryption_algorithm); });
+  return current;
+}
+}  // namespace
 
-  if (!password_encryption_algorithm.has_value()) {
-    throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
+auto CurrentHashAlgorithm() -> PasswordHashAlgorithm { return InternalCurrentHashAlgorithm(); }
+
+void SetHashAlgorithm(std::string_view algo) {
+  auto &current = InternalCurrentHashAlgorithm();
+  current = InternalParseHashAlgorithm(algo);
+}
+
+auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view {
+  return *utils::EnumToString<PasswordHashAlgorithm>(hash_algo, password_hash_mappings);
+}
+
+bool HashedPassword::VerifyPassword(const std::string &password) {
+  switch (hash_algo) {
+    case PasswordHashAlgorithm::BCRYPT:
+      return BCrypt::VerifyPassword(password, password_hash);
+    case PasswordHashAlgorithm::SHA256:
+      return SHA::VerifyPassword(password, password_hash, ONE_SHA_ITERATION);
+    case PasswordHashAlgorithm::SHA256_MULTIPLE:
+      return SHA::VerifyPassword(password, password_hash, MULTIPLE_SHA_ITERATIONS);
   }
+}
 
-  switch (password_encryption_algorithm.value()) {
-    case PasswordEncryptionAlgorithm::BCRYPT:
-      return BCrypt::EncryptPassword(password);
-    case PasswordEncryptionAlgorithm::SHA256:
-      return SHA::EncryptPassword(password, ONE_SHA_ITERATION);
-    case PasswordEncryptionAlgorithm::SHA256_MULTIPLE:
-      return SHA::EncryptPassword(password, MULTIPLE_SHA_ITERATIONS);
+void to_json(nlohmann::json &j, const HashedPassword &p) {
+  j = nlohmann::json{{kHashAlgo, p.hash_algo}, {kPasswordHash, p.password_hash}};
+}
+
+void from_json(const nlohmann::json &j, HashedPassword &p) {
+  // NOLINTNEXTLINE(cppcoreguidelines-init-variables)
+  PasswordHashAlgorithm hash_algo;
+  j.at(kHashAlgo).get_to(hash_algo);
+  auto password_hash = j.value(kPasswordHash, std::string());
+  p = HashedPassword{hash_algo, std::move(password_hash)};
+}
+
+bool HashedPassword::IsSalted() const {
+  switch (hash_algo) {
+    case PasswordHashAlgorithm::BCRYPT:
+      return true;
+    case PasswordHashAlgorithm::SHA256:
+    case PasswordHashAlgorithm::SHA256_MULTIPLE:
+      return SHA::IsSalted(password_hash);
   }
 }
 
diff --git a/src/auth/crypto.hpp b/src/auth/crypto.hpp
index dbceb128b..c5dfc1c05 100644
--- a/src/auth/crypto.hpp
+++ b/src/auth/crypto.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
 // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -8,14 +8,45 @@
 
 #pragma once
 
+#include <json/json.hpp>
+#include <optional>
 #include <string>
 
 namespace memgraph::auth {
-enum class PasswordEncryptionAlgorithm : uint8_t { BCRYPT, SHA256, SHA256_MULTIPLE };
+/// Need to be stable, auth durability depends on this
+enum class PasswordHashAlgorithm : uint8_t { BCRYPT = 0, SHA256 = 1, SHA256_MULTIPLE = 2 };
 
-/// @throw AuthException if unable to encrypt the password.
-std::string EncryptPassword(const std::string &password);
+void SetHashAlgorithm(std::string_view algo);
 
-/// @throw AuthException if unable to verify the password.
-bool VerifyPassword(const std::string &password, const std::string &hash);
+auto CurrentHashAlgorithm() -> PasswordHashAlgorithm;
+
+auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view;
+
+struct HashedPassword {
+  HashedPassword() = default;
+  HashedPassword(PasswordHashAlgorithm hash_algo, std::string password_hash)
+      : hash_algo{hash_algo}, password_hash{std::move(password_hash)} {}
+  HashedPassword(HashedPassword const &) = default;
+  HashedPassword(HashedPassword &&) = default;
+  HashedPassword &operator=(HashedPassword const &) = default;
+  HashedPassword &operator=(HashedPassword &&) = default;
+
+  friend bool operator==(HashedPassword const &, HashedPassword const &) = default;
+
+  bool VerifyPassword(const std::string &password);
+
+  bool IsSalted() const;
+
+  auto HashAlgo() const -> PasswordHashAlgorithm { return hash_algo; }
+
+  friend void to_json(nlohmann::json &j, const HashedPassword &p);
+  friend void from_json(const nlohmann::json &j, HashedPassword &p);
+
+ private:
+  PasswordHashAlgorithm hash_algo{PasswordHashAlgorithm::BCRYPT};
+  std::string password_hash{};
+};
+
+/// @throw AuthException if unable to hash the password.
+HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo = {});
 }  // namespace memgraph::auth
diff --git a/src/auth/models.cpp b/src/auth/models.cpp
index 5415dc08d..a59a73c7b 100644
--- a/src/auth/models.cpp
+++ b/src/auth/models.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
 // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -9,7 +9,6 @@
 #include "auth/models.hpp"
 
 #include <cstdint>
-#include <regex>
 #include <utility>
 
 #include <gflags/gflags.h>
@@ -21,22 +20,26 @@
 #include "query/constants.hpp"
 #include "spdlog/spdlog.h"
 #include "utils/cast.hpp"
-#include "utils/logging.hpp"
-#include "utils/settings.hpp"
 #include "utils/string.hpp"
 
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
-
-inline constexpr std::string_view default_password_regex = ".+";
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
-              "The regular expression that should be used to match the entire "
-              "entered password to ensure its strength.");
-
 namespace memgraph::auth {
 namespace {
 
+constexpr auto kRoleName = "rolename";
+constexpr auto kPermissions = "permissions";
+constexpr auto kGrants = "grants";
+constexpr auto kDenies = "denies";
+constexpr auto kUsername = "username";
+constexpr auto kPasswordHash = "password_hash";
+
+#ifdef MG_ENTERPRISE
+constexpr auto kGlobalPermission = "global_permission";
+constexpr auto kFineGrainedAccessHandler = "fine_grained_access_handler";
+constexpr auto kAllowAll = "allow_all";
+constexpr auto kDefault = "default";
+constexpr auto kDatabases = "databases";
+#endif
+
 // Constant list of all available permissions.
 const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
                                                  Permission::CREATE,
@@ -62,7 +65,8 @@ const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
                                                  Permission::TRANSACTION_MANAGEMENT,
                                                  Permission::STORAGE_MODE,
                                                  Permission::MULTI_DATABASE_EDIT,
-                                                 Permission::MULTI_DATABASE_USE};
+                                                 Permission::MULTI_DATABASE_USE,
+                                                 Permission::COORDINATOR};
 
 }  // namespace
 
@@ -118,6 +122,8 @@ std::string PermissionToString(Permission permission) {
       return "MULTI_DATABASE_EDIT";
     case Permission::MULTI_DATABASE_USE:
       return "MULTI_DATABASE_USE";
+    case Permission::COORDINATOR:
+      return "COORDINATOR";
   }
 }
 
@@ -242,8 +248,9 @@ std::vector<Permission> Permissions::GetDenies() const {
 
 nlohmann::json Permissions::Serialize() const {
   nlohmann::json data = nlohmann::json::object();
-  data["grants"] = grants_;
-  data["denies"] = denies_;
+
+  data[kGrants] = grants_;
+  data[kDenies] = denies_;
   return data;
 }
 
@@ -251,10 +258,10 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
     throw AuthException("Couldn't load permissions data!");
   }
-  if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
+  if (!data[kGrants].is_number_unsigned() || !data[kDenies].is_number_unsigned()) {
     throw AuthException("Couldn't load permissions data!");
   }
-  return Permissions{data["grants"], data["denies"]};
+  return Permissions{data[kGrants], data[kDenies]};
 }
 
 uint64_t Permissions::grants() const { return grants_; }
@@ -316,8 +323,8 @@ nlohmann::json FineGrainedAccessPermissions::Serialize() const {
     return {};
   }
   nlohmann::json data = nlohmann::json::object();
-  data["permissions"] = permissions_;
-  data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
+  data[kPermissions] = permissions_;
+  data[kGlobalPermission] = global_permission_.has_value() ? global_permission_.value() : -1;
   return data;
 }
 
@@ -330,13 +337,13 @@ FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlo
   }
   std::optional<uint64_t> global_permission;
 
-  if (data["global_permission"].empty() || data["global_permission"] == -1) {
+  if (data[kGlobalPermission].empty() || data[kGlobalPermission] == -1) {
     global_permission = std::nullopt;
   } else {
-    global_permission = data["global_permission"];
+    global_permission = data[kGlobalPermission];
   }
 
-  return FineGrainedAccessPermissions(data["permissions"], global_permission);
+  return FineGrainedAccessPermissions(data[kPermissions], global_permission);
 }
 
 const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
@@ -442,13 +449,13 @@ const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermission
 
 nlohmann::json Role::Serialize() const {
   nlohmann::json data = nlohmann::json::object();
-  data["rolename"] = rolename_;
-  data["permissions"] = permissions_.Serialize();
+  data[kRoleName] = rolename_;
+  data[kPermissions] = permissions_.Serialize();
 #ifdef MG_ENTERPRISE
   if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
-    data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
+    data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize();
   } else {
-    data["fine_grained_access_handler"] = {};
+    data[kFineGrainedAccessHandler] = {};
   }
 #endif
   return data;
@@ -458,21 +465,21 @@ Role Role::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
     throw AuthException("Couldn't load role data!");
   }
-  if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
+  if (!data[kRoleName].is_string() || !data[kPermissions].is_object()) {
     throw AuthException("Couldn't load role data!");
   }
-  auto permissions = Permissions::Deserialize(data["permissions"]);
+  auto permissions = Permissions::Deserialize(data[kPermissions]);
 #ifdef MG_ENTERPRISE
   if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
     FineGrainedAccessHandler fine_grained_access_handler;
     // We can have an empty fine_grained if the user was created without a valid license
-    if (data["fine_grained_access_handler"].is_object()) {
-      fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
+    if (data[kFineGrainedAccessHandler].is_object()) {
+      fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]);
     }
-    return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
+    return {data[kRoleName], permissions, std::move(fine_grained_access_handler)};
   }
 #endif
-  return {data["rolename"], permissions};
+  return {data[kRoleName], permissions};
 }
 
 bool operator==(const Role &first, const Role &second) {
@@ -486,13 +493,13 @@ bool operator==(const Role &first, const Role &second) {
 }
 
 #ifdef MG_ENTERPRISE
-void Databases::Add(const std::string &db) {
+void Databases::Add(std::string_view db) {
   if (allow_all_) {
     grants_dbs_.clear();
     allow_all_ = false;
   }
   grants_dbs_.emplace(db);
-  denies_dbs_.erase(db);
+  denies_dbs_.erase(std::string{db});  // TODO: C++23 use transparent key compare
 }
 
 void Databases::Remove(const std::string &db) {
@@ -523,13 +530,13 @@ void Databases::DenyAll() {
   denies_dbs_.clear();
 }
 
-bool Databases::SetDefault(const std::string &db) {
+bool Databases::SetDefault(std::string_view db) {
   if (!Contains(db)) return false;
   default_db_ = db;
   return true;
 }
 
-[[nodiscard]] bool Databases::Contains(const std::string &db) const {
+[[nodiscard]] bool Databases::Contains(std::string_view db) const {
   return !denies_dbs_.contains(db) && (allow_all_ || grants_dbs_.contains(db));
 }
 
@@ -542,10 +549,10 @@ const std::string &Databases::GetDefault() const {
 
 nlohmann::json Databases::Serialize() const {
   nlohmann::json data = nlohmann::json::object();
-  data["grants"] = grants_dbs_;
-  data["denies"] = denies_dbs_;
-  data["allow_all"] = allow_all_;
-  data["default"] = default_db_;
+  data[kGrants] = grants_dbs_;
+  data[kDenies] = denies_dbs_;
+  data[kAllowAll] = allow_all_;
+  data[kDefault] = default_db_;
   return data;
 }
 
@@ -553,22 +560,22 @@ Databases Databases::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
     throw AuthException("Couldn't load database data!");
   }
-  if (!data["grants"].is_structured() || !data["denies"].is_structured() || !data["allow_all"].is_boolean() ||
-      !data["default"].is_string()) {
+  if (!data[kGrants].is_structured() || !data[kDenies].is_structured() || !data[kAllowAll].is_boolean() ||
+      !data[kDefault].is_string()) {
     throw AuthException("Couldn't load database data!");
   }
-  return {data["allow_all"], data["grants"], data["denies"], data["default"]};
+  return {data[kAllowAll], data[kGrants], data[kDenies], data[kDefault]};
 }
 #endif
 
 User::User() = default;
 
 User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
-User::User(const std::string &username, std::string password_hash, const Permissions &permissions)
+User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions)
     : username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), permissions_(permissions) {}
 
 #ifdef MG_ENTERPRISE
-User::User(const std::string &username, std::string password_hash, const Permissions &permissions,
+User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions,
            FineGrainedAccessHandler fine_grained_access_handler, Databases db_access)
     : username_(utils::ToLowerCase(username)),
       password_hash_(std::move(password_hash)),
@@ -578,38 +585,16 @@ User::User(const std::string &username, std::string password_hash, const Permiss
 #endif
 
 bool User::CheckPassword(const std::string &password) {
-  if (password_hash_.empty()) return true;
-  return VerifyPassword(password, password_hash_);
+  return password_hash_ ? password_hash_->VerifyPassword(password) : true;
 }
 
-void User::UpdatePassword(const std::optional<std::string> &password) {
+void User::UpdatePassword(const std::optional<std::string> &password,
+                          std::optional<PasswordHashAlgorithm> algo_override) {
   if (!password) {
-    if (!FLAGS_auth_password_permit_null) {
-      throw AuthException("Null passwords aren't permitted!");
-    }
-    password_hash_ = "";
+    password_hash_.reset();
     return;
   }
-
-  if (FLAGS_auth_password_strength_regex != default_password_regex) {
-    if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings);
-        license_check_result.HasError()) {
-      throw AuthException(
-          "Custom password regex is a Memgraph Enterprise feature. Please set the config "
-          "(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
-          default_password_regex,
-          license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
-    }
-  }
-  std::regex re(FLAGS_auth_password_strength_regex);
-  if (!std::regex_match(*password, re)) {
-    throw AuthException(
-        "The user password doesn't conform to the required strength! Regex: "
-        "\"{}\"",
-        FLAGS_auth_password_strength_regex);
-  }
-
-  password_hash_ = EncryptPassword(*password);
+  password_hash_ = HashPassword(*password, algo_override);
 }
 
 void User::SetRole(const Role &role) { role_.emplace(role); }
@@ -668,16 +653,20 @@ const Role *User::role() const {
 
 nlohmann::json User::Serialize() const {
   nlohmann::json data = nlohmann::json::object();
-  data["username"] = username_;
-  data["password_hash"] = password_hash_;
-  data["permissions"] = permissions_.Serialize();
+  data[kUsername] = username_;
+  if (password_hash_.has_value()) {
+    data[kPasswordHash] = *password_hash_;
+  } else {
+    data[kPasswordHash] = nullptr;
+  }
+  data[kPermissions] = permissions_.Serialize();
 #ifdef MG_ENTERPRISE
   if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
-    data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
-    data["databases"] = database_access_.Serialize();
+    data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize();
+    data[kDatabases] = database_access_.Serialize();
   } else {
-    data["fine_grained_access_handler"] = {};
-    data["databases"] = {};
+    data[kFineGrainedAccessHandler] = {};
+    data[kDatabases] = {};
   }
 #endif
   // The role shouldn't be serialized here, it is stored as a foreign key.
@@ -688,15 +677,23 @@ User User::Deserialize(const nlohmann::json &data) {
   if (!data.is_object()) {
     throw AuthException("Couldn't load user data!");
   }
-  if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
+  auto password_hash_json = data[kPasswordHash];
+  if (!data[kUsername].is_string() || !(password_hash_json.is_object() || password_hash_json.is_null()) ||
+      !data[kPermissions].is_object()) {
     throw AuthException("Couldn't load user data!");
   }
-  auto permissions = Permissions::Deserialize(data["permissions"]);
+
+  std::optional<HashedPassword> password_hash{};
+  if (password_hash_json.is_object()) {
+    password_hash = password_hash_json.get<HashedPassword>();
+  }
+
+  auto permissions = Permissions::Deserialize(data[kPermissions]);
 #ifdef MG_ENTERPRISE
   if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
     Databases db_access;
-    if (data["databases"].is_structured()) {
-      db_access = Databases::Deserialize(data["databases"]);
+    if (data[kDatabases].is_structured()) {
+      db_access = Databases::Deserialize(data[kDatabases]);
     } else {
       // Back-compatibility
       spdlog::warn("User without specified database access. Given access to the default database.");
@@ -705,13 +702,13 @@ User User::Deserialize(const nlohmann::json &data) {
     }
     FineGrainedAccessHandler fine_grained_access_handler;
     // We can have an empty fine_grained if the user was created without a valid license
-    if (data["fine_grained_access_handler"].is_object()) {
-      fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
+    if (data[kFineGrainedAccessHandler].is_object()) {
+      fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]);
     }
-    return {data["username"], data["password_hash"], permissions, std::move(fine_grained_access_handler), db_access};
+    return {data[kUsername], std::move(password_hash), permissions, std::move(fine_grained_access_handler), db_access};
   }
 #endif
-  return {data["username"], data["password_hash"], permissions};
+  return {data[kUsername], std::move(password_hash), permissions};
 }
 
 bool operator==(const User &first, const User &second) {
diff --git a/src/auth/models.hpp b/src/auth/models.hpp
index 9f66d3119..bb6dd2a7a 100644
--- a/src/auth/models.hpp
+++ b/src/auth/models.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
 // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -15,6 +15,7 @@
 
 #include <json/json.hpp>
 #include <utility>
+#include "crypto.hpp"
 #include "dbms/constants.hpp"
 #include "utils/logging.hpp"
 
@@ -48,6 +49,7 @@ enum class Permission : uint64_t {
   STORAGE_MODE = 1U << 22U,
   MULTI_DATABASE_EDIT = 1U << 23U,
   MULTI_DATABASE_USE  = 1U << 24U,
+  COORDINATOR  = 1U << 25U,
 };
 // clang-format on
 
@@ -246,7 +248,7 @@ bool operator==(const Role &first, const Role &second);
 #ifdef MG_ENTERPRISE
 class Databases final {
  public:
-  Databases() : grants_dbs_({dbms::kDefaultDB}), allow_all_(false), default_db_(dbms::kDefaultDB) {}
+  Databases() : grants_dbs_{std::string{dbms::kDefaultDB}}, allow_all_(false), default_db_(dbms::kDefaultDB) {}
 
   Databases(const Databases &) = default;
   Databases &operator=(const Databases &) = default;
@@ -259,7 +261,7 @@ class Databases final {
    *
    * @param db name of the database to grant access to
    */
-  void Add(const std::string &db);
+  void Add(std::string_view db);
 
   /**
    * @brief Remove database to the list of granted access.
@@ -291,7 +293,7 @@ class Databases final {
   /**
    * @brief Set the default database.
    */
-  bool SetDefault(const std::string &db);
+  bool SetDefault(std::string_view db);
 
   /**
    * @brief Checks if access is grated to the database.
@@ -299,7 +301,7 @@ class Databases final {
    * @param db name of the database
    * @return true if allow_all and not denied or granted
    */
-  bool Contains(const std::string &db) const;
+  bool Contains(std::string_view db) const;
 
   bool GetAllowAll() const { return allow_all_; }
   const std::set<std::string, std::less<>> &GetGrants() const { return grants_dbs_; }
@@ -312,7 +314,7 @@ class Databases final {
 
  private:
   Databases(bool allow_all, std::set<std::string, std::less<>> grant, std::set<std::string, std::less<>> deny,
-            std::string default_db = dbms::kDefaultDB)
+            std::string default_db = std::string{dbms::kDefaultDB})
       : grants_dbs_(std::move(grant)),
         denies_dbs_(std::move(deny)),
         allow_all_(allow_all),
@@ -331,9 +333,9 @@ class User final {
   User();
 
   explicit User(const std::string &username);
-  User(const std::string &username, std::string password_hash, const Permissions &permissions);
+  User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions);
 #ifdef MG_ENTERPRISE
-  User(const std::string &username, std::string password_hash, const Permissions &permissions,
+  User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions,
        FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {});
 #endif
   User(const User &) = default;
@@ -345,8 +347,18 @@ class User final {
   /// @throw AuthException if unable to verify the password.
   bool CheckPassword(const std::string &password);
 
+  bool UpgradeHash(const std::string password) {
+    if (!password_hash_) return false;
+    if (password_hash_->IsSalted()) return false;
+
+    auto const algo = password_hash_->HashAlgo();
+    UpdatePassword(password, algo);
+    return true;
+  }
+
   /// @throw AuthException if unable to set the password.
-  void UpdatePassword(const std::optional<std::string> &password = std::nullopt);
+  void UpdatePassword(const std::optional<std::string> &password = {},
+                      std::optional<PasswordHashAlgorithm> algo_override = std::nullopt);
 
   void SetRole(const Role &role);
 
@@ -381,7 +393,7 @@ class User final {
 
  private:
   std::string username_;
-  std::string password_hash_;
+  std::optional<HashedPassword> password_hash_;
   Permissions permissions_;
 #ifdef MG_ENTERPRISE
   FineGrainedAccessHandler fine_grained_access_handler_;
diff --git a/src/communication/bolt/v1/states/handlers.hpp b/src/communication/bolt/v1/states/handlers.hpp
index 3b5a67b17..3ffcb6f55 100644
--- a/src/communication/bolt/v1/states/handlers.hpp
+++ b/src/communication/bolt/v1/states/handlers.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -170,6 +170,7 @@ inline State HandleFailure(TSession &session, const std::exception &e) {
     spdlog::trace("Error trace: {}", p->trace());
   }
   session.encoder_buffer_.Clear();
+
   auto code_message = ExceptionToErrorMessage(e);
   bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}});
   if (!fail_sent) {
diff --git a/src/communication/client.cpp b/src/communication/client.cpp
index bd7fd6e7f..85c0d826a 100644
--- a/src/communication/client.cpp
+++ b/src/communication/client.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,6 +12,7 @@
 #include "communication/client.hpp"
 
 #include "communication/helpers.hpp"
+#include "io/network/network_error.hpp"
 #include "utils/logging.hpp"
 
 namespace memgraph::communication {
@@ -25,7 +26,9 @@ Client::~Client() {
 
 bool Client::Connect(const io::network::Endpoint &endpoint) {
   // Try to establish a socket connection.
-  if (!socket_.Connect(endpoint)) return false;
+  if (!socket_.Connect(endpoint)) {
+    return false;
+  }
 
   // Enable TCP keep alive for all connections.
   // Because we manually always set the `have_more` flag to the socket
diff --git a/src/communication/http/session.hpp b/src/communication/http/session.hpp
index b08ce8f30..cef457065 100644
--- a/src/communication/http/session.hpp
+++ b/src/communication/http/session.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -107,7 +107,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe
   void DoRead() {
     req_ = {};
 
-    ExecuteForStream([this](auto &&stream) {
+    ExecuteForStream([this](auto &stream) {
       boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(kSSLExpirySeconds));
 
       boost::beast::http::async_read(
@@ -129,7 +129,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe
     }
 
     auto async_write = [this](boost::beast::http::response<boost::beast::http::string_body> msg) {
-      ExecuteForStream([this, &msg](auto &&stream) {
+      ExecuteForStream([this, &msg](auto &stream) {
         // The lifetime of the message has to extend
         // for the duration of the async operation so
         // we use a shared_ptr to manage it.
@@ -171,7 +171,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe
   }
 
   auto GetExecutor() {
-    return std::visit(utils::Overloaded{[](auto &&stream) { return stream.get_executor(); }}, stream_);
+    return std::visit(utils::Overloaded{[](auto &stream) { return stream.get_executor(); }}, stream_);
   }
 
   template <typename F>
diff --git a/src/communication/result_stream_faker.hpp b/src/communication/result_stream_faker.hpp
index f8786dd43..779d039cc 100644
--- a/src/communication/result_stream_faker.hpp
+++ b/src/communication/result_stream_faker.hpp
@@ -44,7 +44,7 @@ class ResultStreamFaker {
     std::vector<memgraph::communication::bolt::Value> bvalues;
     bvalues.reserve(values.size());
     for (const auto &value : values) {
-      auto maybe_value = memgraph::glue::ToBoltValue(value, *store_, memgraph::storage::View::NEW);
+      auto maybe_value = memgraph::glue::ToBoltValue(value, store_, memgraph::storage::View::NEW);
       MG_ASSERT(maybe_value.HasValue());
       bvalues.push_back(std::move(*maybe_value));
     }
@@ -56,7 +56,7 @@ class ResultStreamFaker {
   void Summary(const std::map<std::string, memgraph::query::TypedValue> &summary) {
     std::map<std::string, memgraph::communication::bolt::Value> bsummary;
     for (const auto &item : summary) {
-      auto maybe_value = memgraph::glue::ToBoltValue(item.second, *store_, memgraph::storage::View::NEW);
+      auto maybe_value = memgraph::glue::ToBoltValue(item.second, store_, memgraph::storage::View::NEW);
       MG_ASSERT(maybe_value.HasValue());
       bsummary.insert({item.first, std::move(*maybe_value)});
     }
diff --git a/src/coordination/CMakeLists.txt b/src/coordination/CMakeLists.txt
new file mode 100644
index 000000000..e8c4b3735
--- /dev/null
+++ b/src/coordination/CMakeLists.txt
@@ -0,0 +1,29 @@
+add_library(mg-coordination STATIC)
+add_library(mg::coordination ALIAS mg-coordination)
+target_sources(mg-coordination
+        PUBLIC
+        include/coordination/coordinator_client.hpp
+        include/coordination/coordinator_state.hpp
+        include/coordination/coordinator_rpc.hpp
+        include/coordination/coordinator_server.hpp
+        include/coordination/coordinator_config.hpp
+        include/coordination/coordinator_exceptions.hpp
+        include/coordination/coordinator_instance.hpp
+        include/coordination/coordinator_slk.hpp
+        include/coordination/coordinator_data.hpp
+        include/coordination/constants.hpp
+        include/coordination/failover_status.hpp
+        include/coordination/coordinator_cluster_config.hpp
+
+        PRIVATE
+        coordinator_client.cpp
+        coordinator_state.cpp
+        coordinator_rpc.cpp
+        coordinator_server.cpp
+        coordinator_data.cpp
+)
+target_include_directories(mg-coordination PUBLIC include)
+
+target_link_libraries(mg-coordination
+    PUBLIC mg::utils mg::rpc mg::slk mg::io mg::repl_coord_glue lib::rangev3
+)
diff --git a/src/coordination/coordinator_client.cpp b/src/coordination/coordinator_client.cpp
new file mode 100644
index 000000000..93ef3e3af
--- /dev/null
+++ b/src/coordination/coordinator_client.cpp
@@ -0,0 +1,109 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_client.hpp"
+
+#include "coordination/coordinator_config.hpp"
+#include "coordination/coordinator_rpc.hpp"
+#include "replication_coordination_glue/messages.hpp"
+
+namespace memgraph::coordination {
+
+namespace {
+auto CreateClientContext(const memgraph::coordination::CoordinatorClientConfig &config)
+    -> communication::ClientContext {
+  return (config.ssl) ? communication::ClientContext{config.ssl->key_file, config.ssl->cert_file}
+                      : communication::ClientContext{};
+}
+}  // namespace
+
+CoordinatorClient::CoordinatorClient(CoordinatorData *coord_data, CoordinatorClientConfig config,
+                                     HealthCheckCallback succ_cb, HealthCheckCallback fail_cb)
+    : rpc_context_{CreateClientContext(config)},
+      rpc_client_{io::network::Endpoint(io::network::Endpoint::needs_resolving, config.ip_address, config.port),
+                  &rpc_context_},
+      config_{std::move(config)},
+      coord_data_{coord_data},
+      succ_cb_{std::move(succ_cb)},
+      fail_cb_{std::move(fail_cb)} {}
+
+auto CoordinatorClient::InstanceName() const -> std::string { return config_.instance_name; }
+auto CoordinatorClient::SocketAddress() const -> std::string { return rpc_client_.Endpoint().SocketAddress(); }
+
+void CoordinatorClient::StartFrequentCheck() {
+  MG_ASSERT(config_.health_check_frequency_sec > std::chrono::seconds(0),
+            "Health check frequency must be greater than 0");
+
+  instance_checker_.Run(
+      "Coord checker", config_.health_check_frequency_sec, [this, instance_name = config_.instance_name] {
+        try {
+          spdlog::trace("Sending frequent heartbeat to machine {} on {}", instance_name,
+                        rpc_client_.Endpoint().SocketAddress());
+          auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
+          stream.AwaitResponse();
+          succ_cb_(coord_data_, instance_name);
+        } catch (const rpc::RpcFailedException &) {
+          fail_cb_(coord_data_, instance_name);
+        }
+      });
+}
+
+void CoordinatorClient::StopFrequentCheck() { instance_checker_.Stop(); }
+
+void CoordinatorClient::PauseFrequentCheck() { instance_checker_.Pause(); }
+void CoordinatorClient::ResumeFrequentCheck() { instance_checker_.Resume(); }
+
+auto CoordinatorClient::SetSuccCallback(HealthCheckCallback succ_cb) -> void { succ_cb_ = std::move(succ_cb); }
+auto CoordinatorClient::SetFailCallback(HealthCheckCallback fail_cb) -> void { fail_cb_ = std::move(fail_cb); }
+
+auto CoordinatorClient::ReplicationClientInfo() const -> const CoordinatorClientConfig::ReplicationClientInfo & {
+  return config_.replication_client_info;
+}
+
+auto CoordinatorClient::ResetReplicationClientInfo() -> void {
+  // TODO (antoniofilipovic) Sync with Andi on this one
+  // config_.replication_client_info.reset();
+}
+
+auto CoordinatorClient::SendPromoteReplicaToMainRpc(
+    std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info) const -> bool {
+  try {
+    auto stream{rpc_client_.Stream<PromoteReplicaToMainRpc>(std::move(replication_clients_info))};
+    if (!stream.AwaitResponse().success) {
+      spdlog::error("Failed to receive successful RPC failover response!");
+      return false;
+    }
+    return true;
+  } catch (const rpc::RpcFailedException &) {
+    spdlog::error("RPC error occurred while sending failover RPC!");
+  }
+  return false;
+}
+
+auto CoordinatorClient::SendSetToReplicaRpc(CoordinatorClient::ReplClientInfo replication_client_info) const -> bool {
+  try {
+    auto stream{rpc_client_.Stream<SetMainToReplicaRpc>(std::move(replication_client_info))};
+    if (!stream.AwaitResponse().success) {
+      spdlog::error("Failed to set main to replica!");
+      return false;
+    }
+    spdlog::info("Sent request RPC from coordinator to instance to set it as replica!");
+    return true;
+  } catch (const rpc::RpcFailedException &) {
+    spdlog::error("Failed to send failover RPC from coordinator to new main!");
+  }
+  return false;
+}
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/coordinator_data.cpp b/src/coordination/coordinator_data.cpp
new file mode 100644
index 000000000..c236cf753
--- /dev/null
+++ b/src/coordination/coordinator_data.cpp
@@ -0,0 +1,220 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#include "coordination/coordinator_instance.hpp"
+#include "coordination/register_main_replica_coordinator_status.hpp"
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_data.hpp"
+
+#include <range/v3/view.hpp>
+#include <shared_mutex>
+
+namespace memgraph::coordination {
+
+CoordinatorData::CoordinatorData() {
+  auto find_instance = [](CoordinatorData *coord_data, std::string_view instance_name) -> CoordinatorInstance & {
+    auto instance = std::ranges::find_if(
+        coord_data->registered_instances_,
+        [instance_name](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; });
+
+    MG_ASSERT(instance != coord_data->registered_instances_.end(), "Instance {} not found during callback!",
+              instance_name);
+    return *instance;
+  };
+
+  replica_succ_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
+    auto lock = std::lock_guard{coord_data->coord_data_lock_};
+    spdlog::trace("Instance {} performing replica successful callback", instance_name);
+    auto &instance = find_instance(coord_data, instance_name);
+    MG_ASSERT(instance.IsReplica(), "Instance {} is not a replica!", instance_name);
+    instance.UpdateLastResponseTime();
+  };
+
+  replica_fail_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
+    auto lock = std::lock_guard{coord_data->coord_data_lock_};
+    spdlog::trace("Instance {} performing replica failure callback", instance_name);
+    auto &instance = find_instance(coord_data, instance_name);
+    MG_ASSERT(instance.IsReplica(), "Instance {} is not a replica!", instance_name);
+    instance.UpdateInstanceStatus();
+  };
+
+  main_succ_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
+    auto lock = std::lock_guard{coord_data->coord_data_lock_};
+    spdlog::trace("Instance {} performing main successful callback", instance_name);
+    auto &instance = find_instance(coord_data, instance_name);
+    MG_ASSERT(instance.IsMain(), "Instance {} is not a main!", instance_name);
+    instance.UpdateLastResponseTime();
+  };
+
+  main_fail_cb_ = [this, find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
+    auto lock = std::lock_guard{coord_data->coord_data_lock_};
+    spdlog::trace("Instance {} performing main failure callback", instance_name);
+    auto &instance = find_instance(coord_data, instance_name);
+    MG_ASSERT(instance.IsMain(), "Instance {} is not a main!", instance_name);
+    if (bool main_alive = instance.UpdateInstanceStatus(); !main_alive) {
+      spdlog::info("Main instance {} is not alive, starting automatic failover", instance_name);
+      switch (auto failover_status = DoFailover(); failover_status) {
+        using enum DoFailoverStatus;
+        case ALL_REPLICAS_DOWN:
+          spdlog::warn("Failover aborted since all replicas are down!");
+          break;
+        case MAIN_ALIVE:
+          spdlog::warn("Failover aborted since main is alive!");
+          break;
+        case RPC_FAILED:
+          spdlog::warn("Failover aborted since promoting replica to main failed!");
+          break;
+        case SUCCESS:
+          break;
+      }
+    }
+  };
+}
+
+auto CoordinatorData::DoFailover() -> DoFailoverStatus {
+  using ReplicationClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
+
+  auto replica_instances = registered_instances_ | ranges::views::filter(&CoordinatorInstance::IsReplica);
+
+  auto chosen_replica_instance = std::ranges::find_if(replica_instances, &CoordinatorInstance::IsAlive);
+  if (chosen_replica_instance == replica_instances.end()) {
+    return DoFailoverStatus::ALL_REPLICAS_DOWN;
+  }
+
+  chosen_replica_instance->PrepareForFailover();
+
+  std::vector<ReplicationClientInfo> repl_clients_info;
+  repl_clients_info.reserve(std::ranges::distance(replica_instances));
+
+  auto const not_chosen_replica_instance = [&chosen_replica_instance](const CoordinatorInstance &instance) {
+    return instance != *chosen_replica_instance;
+  };
+  auto const not_main = [](const CoordinatorInstance &instance) { return !instance.IsMain(); };
+
+  // TODO (antoniofilipovic): Should we send also data on old MAIN???
+  // TODO: (andi) Don't send replicas which aren't alive
+  for (const auto &unchosen_replica_instance :
+       replica_instances | ranges::views::filter(not_chosen_replica_instance) | ranges::views::filter(not_main)) {
+    repl_clients_info.emplace_back(unchosen_replica_instance.client_.ReplicationClientInfo());
+  }
+
+  if (!chosen_replica_instance->client_.SendPromoteReplicaToMainRpc(std::move(repl_clients_info))) {
+    chosen_replica_instance->RestoreAfterFailedFailover();
+    return DoFailoverStatus::RPC_FAILED;
+  }
+
+  auto old_main = std::ranges::find_if(registered_instances_, &CoordinatorInstance::IsMain);
+  // TODO: (andi) For performing restoration we will have to improve this
+  old_main->client_.PauseFrequentCheck();
+
+  chosen_replica_instance->PostFailover(main_succ_cb_, main_fail_cb_);
+
+  return DoFailoverStatus::SUCCESS;
+}
+
+auto CoordinatorData::ShowInstances() const -> std::vector<CoordinatorInstanceStatus> {
+  std::vector<CoordinatorInstanceStatus> instances_status;
+  instances_status.reserve(registered_instances_.size());
+
+  auto const stringify_repl_role = [](const CoordinatorInstance &instance) -> std::string {
+    if (!instance.IsAlive()) return "";
+    if (instance.IsMain()) return "main";
+    return "replica";
+  };
+
+  auto const instance_to_status =
+      [&stringify_repl_role](const CoordinatorInstance &instance) -> CoordinatorInstanceStatus {
+    return {.instance_name = instance.InstanceName(),
+            .socket_address = instance.SocketAddress(),
+            .replication_role = stringify_repl_role(instance),
+            .is_alive = instance.IsAlive()};
+  };
+
+  {
+    auto lock = std::shared_lock{coord_data_lock_};
+    std::ranges::transform(registered_instances_, std::back_inserter(instances_status), instance_to_status);
+  }
+
+  return instances_status;
+}
+
+auto CoordinatorData::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
+  auto lock = std::lock_guard{coord_data_lock_};
+
+  // Find replica we already registered
+  auto registered_replica = std::find_if(
+      registered_instances_.begin(), registered_instances_.end(),
+      [instance_name](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; });
+
+  // if replica not found...
+  if (registered_replica == registered_instances_.end()) {
+    spdlog::error("You didn't register instance with given name {}", instance_name);
+    return SetInstanceToMainCoordinatorStatus::NO_INSTANCE_WITH_NAME;
+  }
+
+  registered_replica->client_.PauseFrequentCheck();
+
+  std::vector<CoordinatorClientConfig::ReplicationClientInfo> repl_clients_info;
+  repl_clients_info.reserve(registered_instances_.size() - 1);
+  std::ranges::for_each(registered_instances_,
+                        [registered_replica, &repl_clients_info](const CoordinatorInstance &replica) {
+                          if (replica != *registered_replica) {
+                            repl_clients_info.emplace_back(replica.client_.ReplicationClientInfo());
+                          }
+                        });
+
+  // PROMOTE REPLICA TO MAIN
+  // THIS SHOULD FAIL HERE IF IT IS DOWN
+  if (auto result = registered_replica->client_.SendPromoteReplicaToMainRpc(std::move(repl_clients_info)); !result) {
+    registered_replica->client_.ResumeFrequentCheck();
+    return SetInstanceToMainCoordinatorStatus::COULD_NOT_PROMOTE_TO_MAIN;
+  }
+
+  registered_replica->client_.SetSuccCallback(main_succ_cb_);
+  registered_replica->client_.SetFailCallback(main_fail_cb_);
+  registered_replica->replication_role_ = replication_coordination_glue::ReplicationRole::MAIN;
+  registered_replica->client_.ResumeFrequentCheck();
+
+  return SetInstanceToMainCoordinatorStatus::SUCCESS;
+}
+
+auto CoordinatorData::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus {
+  auto lock = std::lock_guard{coord_data_lock_};
+  if (std::ranges::any_of(registered_instances_, [&config](const CoordinatorInstance &instance) {
+        return instance.InstanceName() == config.instance_name;
+      })) {
+    return RegisterInstanceCoordinatorStatus::NAME_EXISTS;
+  }
+
+  if (std::ranges::any_of(registered_instances_, [&config](const CoordinatorInstance &instance) {
+        spdlog::trace("Comparing {} with {}", instance.SocketAddress(), config.SocketAddress());
+        return instance.SocketAddress() == config.SocketAddress();
+      })) {
+    return RegisterInstanceCoordinatorStatus::END_POINT_EXISTS;
+  }
+
+  CoordinatorClientConfig::ReplicationClientInfo replication_client_info_copy = config.replication_client_info;
+
+  // TODO (antoniofilipovic) create and then push back
+  auto *instance = &registered_instances_.emplace_back(this, std::move(config), replica_succ_cb_, replica_fail_cb_,
+                                                       replication_coordination_glue::ReplicationRole::REPLICA);
+  if (auto res = instance->client_.SendSetToReplicaRpc(replication_client_info_copy); !res) {
+    return RegisterInstanceCoordinatorStatus::RPC_FAILED;
+  }
+
+  instance->client_.StartFrequentCheck();
+
+  return RegisterInstanceCoordinatorStatus::SUCCESS;
+}
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/coordinator_rpc.cpp b/src/coordination/coordinator_rpc.cpp
new file mode 100644
index 000000000..e8a16f0e2
--- /dev/null
+++ b/src/coordination/coordinator_rpc.cpp
@@ -0,0 +1,107 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_rpc.hpp"
+
+#include "coordination/coordinator_slk.hpp"
+#include "slk/serialization.hpp"
+
+namespace memgraph {
+
+namespace coordination {
+
+void PromoteReplicaToMainReq::Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+
+void PromoteReplicaToMainReq::Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+
+void PromoteReplicaToMainRes::Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+
+void PromoteReplicaToMainRes::Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+
+void SetMainToReplicaReq::Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+
+void SetMainToReplicaReq::Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+
+void SetMainToReplicaRes::Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+
+void SetMainToReplicaRes::Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+
+}  // namespace coordination
+
+constexpr utils::TypeInfo coordination::PromoteReplicaToMainReq::kType{utils::TypeId::COORD_FAILOVER_REQ,
+                                                                       "CoordPromoteReplicaToMainReq", nullptr};
+
+constexpr utils::TypeInfo coordination::PromoteReplicaToMainRes::kType{utils::TypeId::COORD_FAILOVER_RES,
+                                                                       "CoordPromoteReplicaToMainRes", nullptr};
+
+constexpr utils::TypeInfo coordination::SetMainToReplicaReq::kType{utils::TypeId::COORD_SET_REPL_MAIN_REQ,
+                                                                   "CoordSetReplMainReq", nullptr};
+
+constexpr utils::TypeInfo coordination::SetMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES,
+                                                                   "CoordSetReplMainRes", nullptr};
+
+namespace slk {
+
+void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.success, builder);
+}
+
+void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->success, reader);
+}
+
+void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.replication_clients_info, builder);
+}
+
+void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->replication_clients_info, reader);
+}
+
+void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.replication_client_info, builder);
+}
+
+void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->replication_client_info, reader);
+}
+
+void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.success, builder);
+}
+
+void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->success, reader);
+}
+
+}  // namespace slk
+
+}  // namespace memgraph
+
+#endif
diff --git a/src/coordination/coordinator_server.cpp b/src/coordination/coordinator_server.cpp
new file mode 100644
index 000000000..a8253cf25
--- /dev/null
+++ b/src/coordination/coordinator_server.cpp
@@ -0,0 +1,57 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_server.hpp"
+#include "replication_coordination_glue/messages.hpp"
+
+namespace memgraph::coordination {
+
+namespace {
+
+auto CreateServerContext(const memgraph::coordination::CoordinatorServerConfig &config)
+    -> communication::ServerContext {
+  return (config.ssl) ? communication::ServerContext{config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
+                                                     config.ssl->verify_peer}
+                      : communication::ServerContext{};
+}
+
+// NOTE: The coordinator server doesn't more than 1 processing thread - each replica can
+// have only a single coordinator server. Also, the single-threaded guarantee
+// simplifies the rest of the implementation.
+constexpr auto kCoordinatorServerThreads = 1;
+
+}  // namespace
+
+CoordinatorServer::CoordinatorServer(const CoordinatorServerConfig &config)
+    : rpc_server_context_{CreateServerContext(config)},
+      rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
+                  kCoordinatorServerThreads} {
+  rpc_server_.Register<replication_coordination_glue::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
+    spdlog::debug("Received FrequentHeartbeatRpc on coordinator server");
+    replication_coordination_glue::FrequentHeartbeatHandler(req_reader, res_builder);
+  });
+}
+
+CoordinatorServer::~CoordinatorServer() {
+  if (rpc_server_.IsRunning()) {
+    auto const &endpoint = rpc_server_.endpoint();
+    spdlog::trace("Closing coordinator server on {}:{}", endpoint.address, endpoint.port);
+    rpc_server_.Shutdown();
+  }
+  rpc_server_.AwaitShutdown();
+}
+
+bool CoordinatorServer::Start() { return rpc_server_.Start(); }
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/coordinator_state.cpp b/src/coordination/coordinator_state.cpp
new file mode 100644
index 000000000..60ec458ac
--- /dev/null
+++ b/src/coordination/coordinator_state.cpp
@@ -0,0 +1,89 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_state.hpp"
+
+#include "coordination/coordinator_config.hpp"
+#include "coordination/register_main_replica_coordinator_status.hpp"
+#include "flags/replication.hpp"
+#include "spdlog/spdlog.h"
+#include "utils/logging.hpp"
+#include "utils/variant_helpers.hpp"
+
+#include <algorithm>
+
+namespace memgraph::coordination {
+
+CoordinatorState::CoordinatorState() {
+  MG_ASSERT(!(FLAGS_coordinator && FLAGS_coordinator_server_port),
+            "Instance cannot be a coordinator and have registered coordinator server.");
+
+  spdlog::info("Executing coordinator constructor");
+  if (FLAGS_coordinator_server_port) {
+    spdlog::info("Coordinator server port set");
+    auto const config = CoordinatorServerConfig{
+        .ip_address = kDefaultReplicationServerIp,
+        .port = static_cast<uint16_t>(FLAGS_coordinator_server_port),
+    };
+    spdlog::info("Executing coordinator constructor main replica");
+
+    data_ = CoordinatorMainReplicaData{.coordinator_server_ = std::make_unique<CoordinatorServer>(config)};
+  }
+}
+
+auto CoordinatorState::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus {
+  MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
+            "Coordinator cannot register replica since variant holds wrong alternative");
+
+  return std::visit(
+      memgraph::utils::Overloaded{
+          [](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
+            return RegisterInstanceCoordinatorStatus::NOT_COORDINATOR;
+          },
+          [config](CoordinatorData &coordinator_data) { return coordinator_data.RegisterInstance(config); }},
+      data_);
+}
+
+auto CoordinatorState::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
+  MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
+            "Coordinator cannot register replica since variant holds wrong alternative");
+
+  return std::visit(
+      memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
+                                    return SetInstanceToMainCoordinatorStatus::NOT_COORDINATOR;
+                                  },
+                                  [&instance_name](CoordinatorData &coordinator_data) {
+                                    return coordinator_data.SetInstanceToMain(instance_name);
+                                  }},
+      data_);
+}
+
+auto CoordinatorState::ShowInstances() const -> std::vector<CoordinatorInstanceStatus> {
+  MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
+            "Can't call show instances on data_, as variant holds wrong alternative");
+  return std::get<CoordinatorData>(data_).ShowInstances();
+}
+
+[[nodiscard]] auto CoordinatorState::DoFailover() -> DoFailoverStatus {
+  MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), "Cannot do failover since variant holds wrong alternative");
+  auto &coord_state = std::get<CoordinatorData>(data_);
+  return coord_state.DoFailover();
+}
+
+auto CoordinatorState::GetCoordinatorServer() const -> CoordinatorServer & {
+  MG_ASSERT(std::holds_alternative<CoordinatorMainReplicaData>(data_),
+            "Cannot get coordinator server since variant holds wrong alternative");
+  return *std::get<CoordinatorMainReplicaData>(data_).coordinator_server_;
+}
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/constants.hpp b/src/coordination/include/coordination/constants.hpp
new file mode 100644
index 000000000..819b9fa05
--- /dev/null
+++ b/src/coordination/include/coordination/constants.hpp
@@ -0,0 +1,22 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+namespace memgraph::coordination {
+
+#ifdef MG_EXPERIMENTAL_HIGH_AVAILABILITY
+constexpr bool allow_ha = true;
+#else
+constexpr bool allow_ha = false;
+#endif
+
+}  // namespace memgraph::coordination
diff --git a/src/coordination/include/coordination/coordinator_client.hpp b/src/coordination/include/coordination/coordinator_client.hpp
new file mode 100644
index 000000000..1bc361a57
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_client.hpp
@@ -0,0 +1,77 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_config.hpp"
+#include "rpc/client.hpp"
+#include "utils/scheduler.hpp"
+
+namespace memgraph::coordination {
+
+class CoordinatorData;
+using HealthCheckCallback = std::function<void(CoordinatorData *, std::string_view)>;
+
+class CoordinatorClient {
+ public:
+  using ReplClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
+  using ReplicationClientsInfo = std::vector<ReplClientInfo>;
+
+  explicit CoordinatorClient(CoordinatorData *coord_data_, CoordinatorClientConfig config, HealthCheckCallback succ_cb,
+                             HealthCheckCallback fail_cb);
+
+  ~CoordinatorClient() = default;
+
+  CoordinatorClient(CoordinatorClient &) = delete;
+  CoordinatorClient &operator=(CoordinatorClient const &) = delete;
+
+  CoordinatorClient(CoordinatorClient &&) noexcept = delete;
+  CoordinatorClient &operator=(CoordinatorClient &&) noexcept = delete;
+
+  void StartFrequentCheck();
+  void StopFrequentCheck();
+  void PauseFrequentCheck();
+  void ResumeFrequentCheck();
+
+  auto InstanceName() const -> std::string;
+  auto SocketAddress() const -> std::string;
+
+  auto SendPromoteReplicaToMainRpc(ReplicationClientsInfo replication_clients_info) const -> bool;
+
+  auto ReplicationClientInfo() const -> const ReplClientInfo &;
+  auto ResetReplicationClientInfo() -> void;
+
+  auto SendSetToReplicaRpc(ReplClientInfo replication_client_info) const -> bool;
+
+  auto SetSuccCallback(HealthCheckCallback succ_cb) -> void;
+  auto SetFailCallback(HealthCheckCallback fail_cb) -> void;
+
+  friend bool operator==(CoordinatorClient const &first, CoordinatorClient const &second) {
+    return first.config_ == second.config_;
+  }
+
+ private:
+  utils::Scheduler instance_checker_;
+
+  // TODO: (andi) Pimpl?
+  communication::ClientContext rpc_context_;
+  mutable rpc::Client rpc_client_;
+
+  CoordinatorClientConfig config_;
+  CoordinatorData *coord_data_;
+  HealthCheckCallback succ_cb_;
+  HealthCheckCallback fail_cb_;
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_cluster_config.hpp b/src/coordination/include/coordination/coordinator_cluster_config.hpp
new file mode 100644
index 000000000..e1d91ff7d
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_cluster_config.hpp
@@ -0,0 +1,22 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+namespace memgraph::coordination {
+
+struct CoordinatorClusterConfig {
+  static constexpr int alive_response_time_difference_sec_{5};
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_config.hpp b/src/coordination/include/coordination/coordinator_config.hpp
new file mode 100644
index 000000000..bbbed9dd7
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_config.hpp
@@ -0,0 +1,78 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "replication_coordination_glue/mode.hpp"
+
+#include <chrono>
+#include <cstdint>
+#include <optional>
+#include <string>
+
+namespace memgraph::coordination {
+
+inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
+
+struct CoordinatorClientConfig {
+  std::string instance_name;
+  std::string ip_address;
+  uint16_t port{};
+  std::chrono::seconds health_check_frequency_sec{1};
+
+  auto SocketAddress() const -> std::string { return ip_address + ":" + std::to_string(port); }
+
+  // Info which coordinator will send to new main when performing failover
+  struct ReplicationClientInfo {
+    // Must be the same as CoordinatorClientConfig's instance_name
+    std::string instance_name;
+    replication_coordination_glue::ReplicationMode replication_mode{};
+    std::string replication_ip_address;
+    uint16_t replication_port{};
+
+    friend bool operator==(ReplicationClientInfo const &, ReplicationClientInfo const &) = default;
+  };
+
+  // Each instance has replication config in case it fails
+  ReplicationClientInfo replication_client_info;
+
+  struct SSL {
+    std::string key_file;
+    std::string cert_file;
+
+    friend bool operator==(const SSL &, const SSL &) = default;
+  };
+
+  std::optional<SSL> ssl;
+
+  friend bool operator==(CoordinatorClientConfig const &, CoordinatorClientConfig const &) = default;
+};
+
+struct CoordinatorServerConfig {
+  std::string ip_address;
+  uint16_t port{};
+  struct SSL {
+    std::string key_file;
+    std::string cert_file;
+    std::string ca_file;
+    bool verify_peer{};
+    friend bool operator==(SSL const &, SSL const &) = default;
+  };
+
+  std::optional<SSL> ssl;
+
+  friend bool operator==(CoordinatorServerConfig const &, CoordinatorServerConfig const &) = default;
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_data.hpp b/src/coordination/include/coordination/coordinator_data.hpp
new file mode 100644
index 000000000..d14f5e1db
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_data.hpp
@@ -0,0 +1,49 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_instance.hpp"
+#include "coordination/coordinator_instance_status.hpp"
+#include "coordination/coordinator_server.hpp"
+#include "coordination/failover_status.hpp"
+#include "coordination/register_main_replica_coordinator_status.hpp"
+#include "utils/rw_lock.hpp"
+
+#include <list>
+
+namespace memgraph::coordination {
+class CoordinatorData {
+ public:
+  CoordinatorData();
+
+  [[nodiscard]] auto DoFailover() -> DoFailoverStatus;
+
+  [[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
+  [[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
+
+  auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>;
+
+ private:
+  mutable utils::RWLock coord_data_lock_{utils::RWLock::Priority::READ};
+  HealthCheckCallback main_succ_cb_, main_fail_cb_, replica_succ_cb_, replica_fail_cb_;
+  // Must be std::list because we rely on pointer stability
+  std::list<CoordinatorInstance> registered_instances_;
+};
+
+struct CoordinatorMainReplicaData {
+  std::unique_ptr<CoordinatorServer> coordinator_server_;
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_exceptions.hpp b/src/coordination/include/coordination/coordinator_exceptions.hpp
new file mode 100644
index 000000000..708fb81f3
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_exceptions.hpp
@@ -0,0 +1,32 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "utils/exceptions.hpp"
+
+namespace memgraph::coordination {
+class CoordinatorFailoverException final : public utils::BasicException {
+ public:
+  explicit CoordinatorFailoverException(const std::string_view what) noexcept
+      : BasicException("Failover didn't complete successfully: " + std::string(what)) {}
+
+  template <class... Args>
+  explicit CoordinatorFailoverException(fmt::format_string<Args...> fmt, Args &&...args) noexcept
+      : CoordinatorFailoverException(fmt::format(fmt, std::forward<Args>(args)...)) {}
+
+  SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorFailoverException)
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_instance.hpp b/src/coordination/include/coordination/coordinator_instance.hpp
new file mode 100644
index 000000000..31a6d8204
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_instance.hpp
@@ -0,0 +1,77 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_client.hpp"
+#include "coordination/coordinator_cluster_config.hpp"
+#include "replication_coordination_glue/role.hpp"
+
+namespace memgraph::coordination {
+
+class CoordinatorData;
+
+class CoordinatorInstance {
+ public:
+  CoordinatorInstance(CoordinatorData *data, CoordinatorClientConfig config, HealthCheckCallback succ_cb,
+                      HealthCheckCallback fail_cb, replication_coordination_glue::ReplicationRole replication_role)
+      : client_(data, std::move(config), std::move(succ_cb), std::move(fail_cb)),
+        replication_role_(replication_role),
+        is_alive_(true) {}
+
+  CoordinatorInstance(CoordinatorInstance const &other) = delete;
+  CoordinatorInstance &operator=(CoordinatorInstance const &other) = delete;
+  CoordinatorInstance(CoordinatorInstance &&other) noexcept = delete;
+  CoordinatorInstance &operator=(CoordinatorInstance &&other) noexcept = delete;
+  ~CoordinatorInstance() = default;
+
+  auto UpdateInstanceStatus() -> bool {
+    is_alive_ = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - last_response_time_)
+                    .count() < CoordinatorClusterConfig::alive_response_time_difference_sec_;
+    return is_alive_;
+  }
+  auto UpdateLastResponseTime() -> void { last_response_time_ = std::chrono::system_clock::now(); }
+
+  auto InstanceName() const -> std::string { return client_.InstanceName(); }
+  auto SocketAddress() const -> std::string { return client_.SocketAddress(); }
+  auto IsAlive() const -> bool { return is_alive_; }
+
+  auto IsReplica() const -> bool {
+    return replication_role_ == replication_coordination_glue::ReplicationRole::REPLICA;
+  }
+  auto IsMain() const -> bool { return replication_role_ == replication_coordination_glue::ReplicationRole::MAIN; }
+
+  auto PrepareForFailover() -> void { client_.PauseFrequentCheck(); }
+  auto RestoreAfterFailedFailover() -> void { client_.ResumeFrequentCheck(); }
+
+  auto PostFailover(HealthCheckCallback main_succ_cb, HealthCheckCallback main_fail_cb) -> void {
+    replication_role_ = replication_coordination_glue::ReplicationRole::MAIN;
+    client_.SetSuccCallback(std::move(main_succ_cb));
+    client_.SetFailCallback(std::move(main_fail_cb));
+    // Comment with Andi but we shouldn't delete this, what if this MAIN FAILS AGAIN
+    // client_.ResetReplicationClientInfo();
+    client_.ResumeFrequentCheck();
+  }
+
+  CoordinatorClient client_;
+  replication_coordination_glue::ReplicationRole replication_role_;
+  std::chrono::system_clock::time_point last_response_time_{};
+  bool is_alive_{false};
+
+  friend bool operator==(CoordinatorInstance const &first, CoordinatorInstance const &second) {
+    return first.client_ == second.client_ && first.replication_role_ == second.replication_role_;
+  }
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_instance_status.hpp b/src/coordination/include/coordination/coordinator_instance_status.hpp
new file mode 100644
index 000000000..2a0a3a985
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_instance_status.hpp
@@ -0,0 +1,31 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "io/network/endpoint.hpp"
+
+#include <string_view>
+
+namespace memgraph::coordination {
+
+struct CoordinatorInstanceStatus {
+  std::string instance_name;
+  std::string socket_address;
+  std::string replication_role;
+  bool is_alive;
+};
+
+}  // namespace memgraph::coordination
+
+#endif
diff --git a/src/coordination/include/coordination/coordinator_rpc.hpp b/src/coordination/include/coordination/coordinator_rpc.hpp
new file mode 100644
index 000000000..99996ef52
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_rpc.hpp
@@ -0,0 +1,104 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_config.hpp"
+#include "rpc/messages.hpp"
+#include "slk/serialization.hpp"
+
+namespace memgraph::coordination {
+
+struct PromoteReplicaToMainReq {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
+  static void Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder);
+
+  explicit PromoteReplicaToMainReq(std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info)
+      : replication_clients_info(std::move(replication_clients_info)) {}
+  PromoteReplicaToMainReq() = default;
+
+  std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info;
+};
+
+struct PromoteReplicaToMainRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader);
+  static void Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder);
+
+  explicit PromoteReplicaToMainRes(bool success) : success(success) {}
+  PromoteReplicaToMainRes() = default;
+
+  bool success;
+};
+
+using PromoteReplicaToMainRpc = rpc::RequestResponse<PromoteReplicaToMainReq, PromoteReplicaToMainRes>;
+
+struct SetMainToReplicaReq {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
+  static void Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
+
+  explicit SetMainToReplicaReq(CoordinatorClientConfig::ReplicationClientInfo replication_client_info)
+      : replication_client_info(std::move(replication_client_info)) {}
+
+  SetMainToReplicaReq() = default;
+
+  CoordinatorClientConfig::ReplicationClientInfo replication_client_info;
+};
+
+struct SetMainToReplicaRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
+  static void Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
+
+  explicit SetMainToReplicaRes(bool success) : success(success) {}
+  SetMainToReplicaRes() = default;
+
+  bool success;
+};
+
+using SetMainToReplicaRpc = rpc::RequestResponse<SetMainToReplicaReq, SetMainToReplicaRes>;
+
+}  // namespace memgraph::coordination
+
+// SLK serialization declarations
+namespace memgraph::slk {
+
+void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
+
+}  // namespace memgraph::slk
+
+#endif
diff --git a/src/coordination/include/coordination/coordinator_server.hpp b/src/coordination/include/coordination/coordinator_server.hpp
new file mode 100644
index 000000000..2a261bc32
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_server.hpp
@@ -0,0 +1,44 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_config.hpp"
+#include "rpc/server.hpp"
+
+namespace memgraph::coordination {
+
+class CoordinatorServer {
+ public:
+  explicit CoordinatorServer(const CoordinatorServerConfig &config);
+  CoordinatorServer(const CoordinatorServer &) = delete;
+  CoordinatorServer(CoordinatorServer &&) = delete;
+  CoordinatorServer &operator=(const CoordinatorServer &) = delete;
+  CoordinatorServer &operator=(CoordinatorServer &&) = delete;
+
+  virtual ~CoordinatorServer();
+
+  bool Start();
+
+  template <typename TRequestResponse, typename F>
+  void Register(F &&callback) {
+    rpc_server_.Register<TRequestResponse>(std::forward<F>(callback));
+  }
+
+ private:
+  communication::ServerContext rpc_server_context_;
+  rpc::Server rpc_server_;
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/coordinator_slk.hpp b/src/coordination/include/coordination/coordinator_slk.hpp
new file mode 100644
index 000000000..49834be41
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_slk.hpp
@@ -0,0 +1,38 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_config.hpp"
+#include "slk/serialization.hpp"
+#include "slk/streams.hpp"
+
+namespace memgraph::slk {
+
+using ReplicationClientInfo = coordination::CoordinatorClientConfig::ReplicationClientInfo;
+
+inline void Save(const ReplicationClientInfo &obj, Builder *builder) {
+  Save(obj.instance_name, builder);
+  Save(obj.replication_mode, builder);
+  Save(obj.replication_ip_address, builder);
+  Save(obj.replication_port, builder);
+}
+
+inline void Load(ReplicationClientInfo *obj, Reader *reader) {
+  Load(&obj->instance_name, reader);
+  Load(&obj->replication_mode, reader);
+  Load(&obj->replication_ip_address, reader);
+  Load(&obj->replication_port, reader);
+}
+}  // namespace memgraph::slk
+#endif
diff --git a/src/coordination/include/coordination/coordinator_state.hpp b/src/coordination/include/coordination/coordinator_state.hpp
new file mode 100644
index 000000000..9cf2d2471
--- /dev/null
+++ b/src/coordination/include/coordination/coordinator_state.hpp
@@ -0,0 +1,53 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "coordination/coordinator_data.hpp"
+#include "coordination/coordinator_instance_status.hpp"
+#include "coordination/coordinator_server.hpp"
+#include "coordination/failover_status.hpp"
+#include "coordination/register_main_replica_coordinator_status.hpp"
+
+#include <variant>
+
+namespace memgraph::coordination {
+
+class CoordinatorState {
+ public:
+  CoordinatorState();
+  ~CoordinatorState() = default;
+
+  CoordinatorState(const CoordinatorState &) = delete;
+  CoordinatorState &operator=(const CoordinatorState &) = delete;
+
+  CoordinatorState(CoordinatorState &&) noexcept = delete;
+  CoordinatorState &operator=(CoordinatorState &&) noexcept = delete;
+
+  [[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
+
+  [[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
+
+  auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>;
+
+  // The client code must check that the server exists before calling this method.
+  auto GetCoordinatorServer() const -> CoordinatorServer &;
+
+  [[nodiscard]] auto DoFailover() -> DoFailoverStatus;
+
+ private:
+  std::variant<CoordinatorData, CoordinatorMainReplicaData> data_;
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/replication/include/replication/role.hpp b/src/coordination/include/coordination/failover_status.hpp
similarity index 70%
rename from src/replication/include/replication/role.hpp
rename to src/coordination/include/coordination/failover_status.hpp
index 54fbab9f0..9cfa0ffe6 100644
--- a/src/replication/include/replication/role.hpp
+++ b/src/coordination/include/coordination/failover_status.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,8 +11,11 @@
 
 #pragma once
 
-#include <cstdint>
-namespace memgraph::replication {
+#ifdef MG_ENTERPRISE
 
-enum class ReplicationRole : uint8_t { MAIN, REPLICA };
-}
+#include <cstdint>
+
+namespace memgraph::coordination {
+enum class DoFailoverStatus : uint8_t { SUCCESS, ALL_REPLICAS_DOWN, MAIN_ALIVE, RPC_FAILED };
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/coordination/include/coordination/register_main_replica_coordinator_status.hpp b/src/coordination/include/coordination/register_main_replica_coordinator_status.hpp
new file mode 100644
index 000000000..acb191bfd
--- /dev/null
+++ b/src/coordination/include/coordination/register_main_replica_coordinator_status.hpp
@@ -0,0 +1,37 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include <cstdint>
+
+namespace memgraph::coordination {
+
+enum class RegisterInstanceCoordinatorStatus : uint8_t {
+  NAME_EXISTS,
+  END_POINT_EXISTS,
+  COULD_NOT_BE_PERSISTED,
+  NOT_COORDINATOR,
+  RPC_FAILED,
+  SUCCESS
+};
+
+enum class SetInstanceToMainCoordinatorStatus : uint8_t {
+  NO_INSTANCE_WITH_NAME,
+  NOT_COORDINATOR,
+  SUCCESS,
+  COULD_NOT_PROMOTE_TO_MAIN,
+};
+
+}  // namespace memgraph::coordination
+#endif
diff --git a/src/dbms/CMakeLists.txt b/src/dbms/CMakeLists.txt
index f1df4985a..9cd94c44c 100644
--- a/src/dbms/CMakeLists.txt
+++ b/src/dbms/CMakeLists.txt
@@ -1,3 +1,2 @@
-
-add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp)
-target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query)
+add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp coordinator_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp coordinator_handlers.cpp)
+target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query mg-replication mg-coordination)
diff --git a/src/dbms/constants.hpp b/src/dbms/constants.hpp
index e7ea9987b..a0e9f6f22 100644
--- a/src/dbms/constants.hpp
+++ b/src/dbms/constants.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,7 +13,8 @@
 
 namespace memgraph::dbms {
 
-constexpr static const char *kDefaultDB = "memgraph";  //!< Name of the default database
+constexpr std::string_view kDefaultDB = "memgraph";        //!< Name of the default database
+constexpr std::string_view kMultiTenantDir = "databases";  //!< Name of the multi-tenant directory
 
 #ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY
 constexpr bool allow_mt_repl = true;
diff --git a/src/dbms/coordinator_handler.cpp b/src/dbms/coordinator_handler.cpp
new file mode 100644
index 000000000..1c062c074
--- /dev/null
+++ b/src/dbms/coordinator_handler.cpp
@@ -0,0 +1,38 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#include "coordination/register_main_replica_coordinator_status.hpp"
+#ifdef MG_ENTERPRISE
+
+#include "dbms/coordinator_handler.hpp"
+
+#include "dbms/dbms_handler.hpp"
+
+namespace memgraph::dbms {
+
+CoordinatorHandler::CoordinatorHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
+
+auto CoordinatorHandler::RegisterInstance(memgraph::coordination::CoordinatorClientConfig config)
+    -> coordination::RegisterInstanceCoordinatorStatus {
+  return dbms_handler_.CoordinatorState().RegisterInstance(config);
+}
+
+auto CoordinatorHandler::SetInstanceToMain(std::string instance_name)
+    -> coordination::SetInstanceToMainCoordinatorStatus {
+  return dbms_handler_.CoordinatorState().SetInstanceToMain(std::move(instance_name));
+}
+
+auto CoordinatorHandler::ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus> {
+  return dbms_handler_.CoordinatorState().ShowInstances();
+}
+}  // namespace memgraph::dbms
+
+#endif
diff --git a/src/dbms/coordinator_handler.hpp b/src/dbms/coordinator_handler.hpp
new file mode 100644
index 000000000..233532cbc
--- /dev/null
+++ b/src/dbms/coordinator_handler.hpp
@@ -0,0 +1,47 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "utils/result.hpp"
+
+#include "coordination/coordinator_config.hpp"
+#include "coordination/coordinator_instance_status.hpp"
+#include "coordination/failover_status.hpp"
+#include "coordination/register_main_replica_coordinator_status.hpp"
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+namespace memgraph::dbms {
+
+class DbmsHandler;
+
+class CoordinatorHandler {
+ public:
+  explicit CoordinatorHandler(DbmsHandler &dbms_handler);
+
+  auto RegisterInstance(coordination::CoordinatorClientConfig config)
+      -> coordination::RegisterInstanceCoordinatorStatus;
+
+  auto SetInstanceToMain(std::string instance_name) -> coordination::SetInstanceToMainCoordinatorStatus;
+
+  auto ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus>;
+
+ private:
+  DbmsHandler &dbms_handler_;
+};
+
+}  // namespace memgraph::dbms
+#endif
diff --git a/src/dbms/coordinator_handlers.cpp b/src/dbms/coordinator_handlers.cpp
new file mode 100644
index 000000000..5c051408e
--- /dev/null
+++ b/src/dbms/coordinator_handlers.cpp
@@ -0,0 +1,153 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#ifdef MG_ENTERPRISE
+
+#include "dbms/coordinator_handlers.hpp"
+#include "dbms/utils.hpp"
+
+#include "coordination/coordinator_exceptions.hpp"
+#include "coordination/coordinator_rpc.hpp"
+#include "dbms/dbms_handler.hpp"
+#include "dbms/replication_client.hpp"
+
+#include "range/v3/view.hpp"
+
+namespace memgraph::dbms {
+
+void CoordinatorHandlers::Register(DbmsHandler &dbms_handler) {
+  auto &server = dbms_handler.CoordinatorState().GetCoordinatorServer();
+
+  server.Register<coordination::PromoteReplicaToMainRpc>(
+      [&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
+        spdlog::info("Received PromoteReplicaToMainRpc");
+        CoordinatorHandlers::PromoteReplicaToMainHandler(dbms_handler, req_reader, res_builder);
+      });
+
+  server.Register<coordination::SetMainToReplicaRpc>(
+      [&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
+        spdlog::info("Received SetMainToReplicaRpc from coordinator server");
+        CoordinatorHandlers::SetMainToReplicaHandler(dbms_handler, req_reader, res_builder);
+      });
+}
+
+void CoordinatorHandlers::SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
+                                                  slk::Builder *res_builder) {
+  auto &repl_state = dbms_handler.ReplicationState();
+
+  if (!repl_state.IsMain()) {
+    spdlog::error("Setting to replica must be performed on main.");
+    slk::Save(coordination::SetMainToReplicaRes{false}, res_builder);
+    return;
+  }
+
+  coordination::SetMainToReplicaReq req;
+  slk::Load(&req, req_reader);
+
+  replication::ReplicationServerConfig clients_config{.ip_address = req.replication_client_info.replication_ip_address,
+                                                      .port = req.replication_client_info.replication_port};
+
+  if (bool success = memgraph::dbms::SetReplicationRoleReplica(dbms_handler, clients_config); !success) {
+    spdlog::error("Setting main to replica failed!");
+    slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+    return;
+  }
+
+  slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
+}
+
+void CoordinatorHandlers::PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
+                                                      slk::Builder *res_builder) {
+  auto &repl_state = dbms_handler.ReplicationState();
+
+  if (!repl_state.IsReplica()) {
+    spdlog::error("Failover must be performed on replica!");
+    slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+    return;
+  }
+
+  auto repl_server_config = std::get<replication::RoleReplicaData>(repl_state.ReplicationData()).config;
+
+  // This can fail because of disk. If it does, the cluster state could get inconsistent.
+  // We don't handle disk issues.
+  if (bool success = memgraph::dbms::DoReplicaToMainPromotion(dbms_handler); !success) {
+    spdlog::error("Promoting replica to main failed!");
+    slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+    return;
+  }
+
+  coordination::PromoteReplicaToMainReq req;
+  slk::Load(&req, req_reader);
+
+  auto const converter = [](const auto &repl_info_config) {
+    return replication::ReplicationClientConfig{
+        .name = repl_info_config.instance_name,
+        .mode = repl_info_config.replication_mode,
+        .ip_address = repl_info_config.replication_ip_address,
+        .port = repl_info_config.replication_port,
+    };
+  };
+
+  MG_ASSERT(
+      std::get<replication::RoleMainData>(repl_state.ReplicationData()).registered_replicas_.empty(),
+      "No replicas should be registered after promoting replica to main and before registering replication clients!");
+
+  // registering replicas
+  for (auto const &config : req.replication_clients_info | ranges::views::transform(converter)) {
+    auto instance_client = repl_state.RegisterReplica(config);
+    if (instance_client.HasError()) {
+      switch (instance_client.GetError()) {
+        // Can't happen, we are already replica
+        case memgraph::replication::RegisterReplicaError::NOT_MAIN:
+          spdlog::error("Failover must be performed to main!");
+          slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+          return;
+        // Can't happen, checked on the coordinator side
+        case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
+          spdlog::error("Replica with the same name already exists!");
+          slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+          return;
+        // Can't happen, checked on the coordinator side
+        case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS:
+          spdlog::error("Replica with the same endpoint already exists!");
+          slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+          return;
+        // We don't handle disk issues
+        case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
+          spdlog::error("Registered replica could not be persisted!");
+          slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
+          return;
+        case memgraph::replication::RegisterReplicaError::SUCCESS:
+          break;
+      }
+    }
+    if (!allow_mt_repl && dbms_handler.All().size() > 1) {
+      spdlog::warn("Multi-tenant replication is currently not supported!");
+    }
+
+    auto &instance_client_ref = *instance_client.GetValue();
+
+    // Update system before enabling individual storage <-> replica clients
+    dbms_handler.SystemRestore(instance_client_ref);
+
+    // TODO: (andi) Policy for register all databases
+    // Will be resolved after deciding about choosing new replica
+    const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler, instance_client_ref);
+    MG_ASSERT(all_clients_good, "Failed to register one or more databases to the REPLICA \"{}\".", config.name);
+
+    StartReplicaClient(dbms_handler, instance_client_ref);
+  }
+
+  slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
+}
+
+}  // namespace memgraph::dbms
+#endif
diff --git a/src/dbms/coordinator_handlers.hpp b/src/dbms/coordinator_handlers.hpp
new file mode 100644
index 000000000..ae4c59a0a
--- /dev/null
+++ b/src/dbms/coordinator_handlers.hpp
@@ -0,0 +1,34 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#ifdef MG_ENTERPRISE
+
+#include "slk/serialization.hpp"
+
+namespace memgraph::dbms {
+
+class DbmsHandler;
+
+class CoordinatorHandlers {
+ public:
+  static void Register(DbmsHandler &dbms_handler);
+
+ private:
+  static void PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
+                                          slk::Builder *res_builder);
+  static void SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
+};
+
+}  // namespace memgraph::dbms
+
+#endif
diff --git a/src/dbms/database.cpp b/src/dbms/database.cpp
index 74ee13892..9a56d400a 100644
--- a/src/dbms/database.cpp
+++ b/src/dbms/database.cpp
@@ -26,7 +26,7 @@ Database::Database(storage::Config config, replication::ReplicationState &repl_s
       streams_{config.durability.storage_directory / "streams"},
       plan_cache_{FLAGS_query_plan_cache_max_size},
       repl_state_(&repl_state) {
-  if (config.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk ||
+  if (config.salient.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk ||
       utils::DirExists(config.disk.main_storage_directory)) {
     storage_ = std::make_unique<storage::DiskStorage>(std::move(config));
   } else {
diff --git a/src/dbms/database.hpp b/src/dbms/database.hpp
index 878fe7672..2d7d3fe88 100644
--- a/src/dbms/database.hpp
+++ b/src/dbms/database.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -68,12 +68,12 @@ class Database {
    */
   std::unique_ptr<storage::Storage::Accessor> Access(
       std::optional<storage::IsolationLevel> override_isolation_level = {}) {
-    return storage_->Access(override_isolation_level, repl_state_->IsMain());
+    return storage_->Access(repl_state_->GetRole(), override_isolation_level);
   }
 
   std::unique_ptr<storage::Storage::Accessor> UniqueAccess(
       std::optional<storage::IsolationLevel> override_isolation_level = {}) {
-    return storage_->UniqueAccess(override_isolation_level, repl_state_->IsMain());
+    return storage_->UniqueAccess(repl_state_->GetRole(), override_isolation_level);
   }
 
   /**
@@ -81,7 +81,14 @@ class Database {
    *
    * @return const std::string&
    */
-  const std::string &id() const { return storage_->id(); }
+  const std::string &name() const { return storage_->name(); }
+
+  /**
+   * @brief Unique storage identified (uuid)
+   *
+   * @return const utils::UUID&
+   */
+  const utils::UUID &uuid() const { return storage_->uuid(); }
 
   /**
    * @brief Returns the storage configuration
@@ -103,9 +110,9 @@ class Database {
    * @param force_directory Use the configured directory, do not try to decipher the multi-db version
    * @return DatabaseInfo
    */
-  DatabaseInfo GetInfo(bool force_directory = false) const {
+  DatabaseInfo GetInfo(bool force_directory, replication_coordination_glue::ReplicationRole replication_role) const {
     DatabaseInfo info;
-    info.storage_info = storage_->GetInfo(force_directory);
+    info.storage_info = storage_->GetInfo(force_directory, replication_role);
     info.triggers = trigger_store_.GetTriggerInfo().size();
     info.streams = streams_.GetStreamInfo().size();
     return info;
diff --git a/src/dbms/database_handler.hpp b/src/dbms/database_handler.hpp
index 617e614c3..de5f813ba 100644
--- a/src/dbms/database_handler.hpp
+++ b/src/dbms/database_handler.hpp
@@ -51,7 +51,7 @@ class DatabaseHandler : public Handler<Database> {
    * @param config Storage configuration
    * @return HandlerT::NewResult
    */
-  HandlerT::NewResult New(std::string_view name, storage::Config config, replication::ReplicationState &repl_state) {
+  HandlerT::NewResult New(storage::Config config, replication::ReplicationState &repl_state) {
     // Control that no one is using the same data directory
     if (std::any_of(begin(), end(), [&](auto &elem) {
           auto db_acc = elem.second.access();
@@ -61,8 +61,7 @@ class DatabaseHandler : public Handler<Database> {
       spdlog::info("Tried to generate new storage using a claimed directory.");
       return NewError::EXISTS;
     }
-    config.name = name;  // Set storage id via config
-    return HandlerT::New(std::piecewise_construct, name, config, repl_state);
+    return HandlerT::New(std::piecewise_construct, config.salient.name, config, repl_state);
   }
 
   /**
diff --git a/src/dbms/dbms_handler.cpp b/src/dbms/dbms_handler.cpp
index 0af9364bf..7222c4461 100644
--- a/src/dbms/dbms_handler.cpp
+++ b/src/dbms/dbms_handler.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,56 +11,205 @@
 
 #include "dbms/dbms_handler.hpp"
 
+#include "dbms/coordinator_handlers.hpp"
+#include "flags/replication.hpp"
+
+#include <cstdint>
+#include <filesystem>
+
+#include "dbms/constants.hpp"
+#include "dbms/global.hpp"
+#include "dbms/replication_client.hpp"
+#include "spdlog/spdlog.h"
+#include "utils/exceptions.hpp"
+#include "utils/logging.hpp"
+#include "utils/uuid.hpp"
+
 namespace memgraph::dbms {
+
 #ifdef MG_ENTERPRISE
+
+namespace {
+constexpr std::string_view kDBPrefix = "database:";                               // Key prefix for database durability
+constexpr std::string_view kLastCommitedSystemTsKey = "last_commited_system_ts";  // Key for timestamp durability
+}  // namespace
+
+struct Durability {
+  enum class DurabilityVersion : uint8_t {
+    V0 = 0,
+    V1,
+  };
+
+  struct VersionException : public utils::BasicException {
+    VersionException() : utils::BasicException("Unsupported durability version!") {}
+  };
+
+  struct UnknownVersionException : public utils::BasicException {
+    UnknownVersionException() : utils::BasicException("Unable to parse the durability version!") {}
+  };
+
+  struct MigrationException : public utils::BasicException {
+    MigrationException() : utils::BasicException("Failed to migrate to the current durability version!") {}
+  };
+
+  static DurabilityVersion VersionCheck(std::optional<std::string_view> val) {
+    if (!val) {
+      return DurabilityVersion::V0;
+    }
+    if (val == "V1") {
+      return DurabilityVersion::V1;
+    }
+    throw UnknownVersionException();
+  };
+
+  static auto GenKey(std::string_view name) -> std::string { return fmt::format("{}{}", kDBPrefix, name); }
+
+  static auto GenVal(utils::UUID uuid, std::filesystem::path rel_dir) {
+    nlohmann::json json;
+    json["uuid"] = uuid;
+    json["rel_dir"] = rel_dir;
+    // TODO: Serialize the configuration
+    return json.dump();
+  }
+
+  static void Migrate(kvstore::KVStore *durability, const std::filesystem::path &root) {
+    const auto ver_val = durability->Get("version");
+    const auto ver = VersionCheck(ver_val);
+
+    std::map<std::string, std::string> to_put;
+    std::vector<std::string> to_delete;
+
+    // Update from V0 to V1
+    if (ver == DurabilityVersion::V0) {
+      for (const auto &[key, val] : *durability) {
+        if (key == "version") continue;  // Reserved key
+        // Generate a UUID
+        auto const uuid = utils::UUID();
+        // New json values
+        auto new_key = GenKey(key);
+        auto path = root;
+        if (key != kDefaultDB) {  // Special case for non-default DBs
+          // Move directory to new UUID dir
+          path = root / kMultiTenantDir / std::string{uuid};
+          std::filesystem::path old_dir(root / kMultiTenantDir / key);
+          std::error_code ec;
+          std::filesystem::rename(old_dir, path, ec);
+          MG_ASSERT(!ec, "Failed to upgrade durability: cannot move default directory.");
+        }
+        // Generate json and update value
+        auto new_data = GenVal(uuid, std::filesystem::relative(path, root));
+        to_put.emplace(std::move(new_key), std::move(new_data));
+        to_delete.emplace_back(key);
+      }
+    }
+
+    // Set version
+    durability->Put("version", "V1");
+    // Update to the new key-value pairs
+    if (!durability->PutAndDeleteMultiple(to_put, to_delete)) {
+      throw MigrationException();
+    }
+  }
+};
+
 DbmsHandler::DbmsHandler(
     storage::Config config,
     memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
-    bool recovery_on_startup, bool delete_on_drop)
-    : default_config_{std::move(config)},
-      delete_on_drop_(delete_on_drop),
-      repl_state_{ReplicationStateRootPath(default_config_)} {
+    bool recovery_on_startup)
+    : default_config_{std::move(config)}, repl_state_{ReplicationStateRootPath(default_config_)} {
   // TODO: Decouple storage config from dbms config
   // TODO: Save individual db configs inside the kvstore and restore from there
-  storage::UpdatePaths(default_config_, default_config_.durability.storage_directory / "databases");
-  const auto &db_dir = default_config_.durability.storage_directory;
+
+  /*
+   * FILESYSTEM MANIPULATION
+   */
+  const auto &root = default_config_.durability.storage_directory;
+  storage::UpdatePaths(default_config_, root);
+  const auto &db_dir = default_config_.durability.storage_directory / kMultiTenantDir;
+  // TODO: Unify durability and wal
   const auto durability_dir = db_dir / ".durability";
   utils::EnsureDirOrDie(db_dir);
   utils::EnsureDirOrDie(durability_dir);
   durability_ = std::make_unique<kvstore::KVStore>(durability_dir);
 
-  // Generate the default database
-  MG_ASSERT(!NewDefault_().HasError(), "Failed while creating the default DB.");
+  /*
+   * DURABILITY
+   */
+  // Migrate durability
+  Durability::Migrate(durability_.get(), root);
+  auto directories = std::set{std::string{kDefaultDB}};
 
   // Recover previous databases
   if (recovery_on_startup) {
-    for (const auto &[name, _] : *durability_) {
-      if (name == kDefaultDB) continue;  // Already set
-      spdlog::info("Restoring database {}.", name);
-      MG_ASSERT(!New_(name).HasError(), "Failed while creating database {}.", name);
+    auto it = durability_->begin(std::string(kDBPrefix));
+    auto end = durability_->end(std::string(kDBPrefix));
+    for (; it != end; ++it) {
+      const auto &[key, config_json] = *it;
+      const auto name = key.substr(kDBPrefix.size());
+      auto json = nlohmann::json::parse(config_json);
+      const auto uuid = json.at("uuid").get<utils::UUID>();
+      const auto rel_dir = json.at("rel_dir").get<std::filesystem::path>();
+      spdlog::info("Restoring database {} at {}.", name, rel_dir);
+      auto new_db = New_(name, uuid, rel_dir);
+      MG_ASSERT(!new_db.HasError(), "Failed while creating database {}.", name);
+      directories.emplace(rel_dir.filename());
       spdlog::info("Database {} restored.", name);
     }
+    // Read the last timestamp
+    auto lcst = durability_->Get(kLastCommitedSystemTsKey);
+    if (lcst) {
+      last_commited_system_timestamp_ = std::stoul(*lcst);
+      system_timestamp_ = last_commited_system_timestamp_;
+    }
   } else {  // Clear databases from the durability list and auth
     auto locked_auth = auth->Lock();
-    for (const auto &[name, _] : *durability_) {
+    auto it = durability_->begin(std::string{kDBPrefix});
+    auto end = durability_->end(std::string{kDBPrefix});
+    for (; it != end; ++it) {
+      const auto &[key, _] = *it;
+      const auto name = key.substr(kDBPrefix.size());
       if (name == kDefaultDB) continue;
       locked_auth->DeleteDatabase(name);
-      durability_->Delete(name);
+      durability_->Delete(key);
+    }
+    // Delete the last timestamp
+    durability_->Delete(kLastCommitedSystemTsKey);
+  }
+
+  /*
+   * DATABASES CLEAN UP
+   */
+  // Clean the unused directories
+  for (const auto &entry : std::filesystem::directory_iterator(db_dir)) {
+    const auto &name = entry.path().filename().string();
+    if (entry.is_directory() && !name.empty() && name.front() != '.') {
+      auto itr = directories.find(name);
+      if (itr == directories.end()) {
+        std::error_code dummy;
+        std::filesystem::remove_all(entry, dummy);
+      } else {
+        directories.erase(itr);
+      }
     }
   }
 
+  /*
+   * DEFAULT DB SETUP
+   */
+  // Setup the default DB
+  SetupDefault_();
+
+  /*
+   * REPLICATION RECOVERY AND STARTUP
+   */
   // Startup replication state (if recovered at startup)
-  auto replica = [this](replication::RoleReplicaData const &data) {
-    // Register handlers
-    InMemoryReplicationHandlers::Register(this, *data.server);
-    if (!data.server->Start()) {
-      spdlog::error("Unable to start the replication server.");
-      return false;
-    }
-    return true;
-  };
-  // Replication frequent check start
+  auto replica = [this](replication::RoleReplicaData const &data) { return StartRpcServer(*this, data); };
+  // Replication recovery and frequent check start
   auto main = [this](replication::RoleMainData &data) {
+    for (auto &client : data.registered_replicas_) {
+      SystemRestore(client);
+    }
+    ForEach([this](DatabaseAccess db) { RecoverReplication(db); });
     for (auto &client : data.registered_replicas_) {
       StartReplicaClient(*this, client);
     }
@@ -69,7 +218,232 @@ DbmsHandler::DbmsHandler(
   // Startup proccess for main/replica
   MG_ASSERT(std::visit(memgraph::utils::Overloaded{replica, main}, repl_state_.ReplicationData()),
             "Replica recovery failure!");
-}
-#endif
 
+  // Warning
+  if (default_config_.durability.snapshot_wal_mode == storage::Config::Durability::SnapshotWalMode::DISABLED &&
+      repl_state_.IsMain()) {
+    spdlog::warn(
+        "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please "
+        "consider "
+        "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
+        "without write-ahead logs this instance is not replicating any data.");
+  }
+
+  // MAIN or REPLICA instance
+  if (FLAGS_coordinator_server_port) {
+    CoordinatorHandlers::Register(*this);
+    MG_ASSERT(coordinator_state_.GetCoordinatorServer().Start(), "Failed to start coordinator server!");
+  }
+}
+
+DbmsHandler::DeleteResult DbmsHandler::TryDelete(std::string_view db_name) {
+  std::lock_guard<LockT> wr(lock_);
+  if (db_name == kDefaultDB) {
+    // MSG cannot delete the default db
+    return DeleteError::DEFAULT_DB;
+  }
+
+  // Get DB config for the UUID and disk clean up
+  const auto conf = db_handler_.GetConfig(db_name);
+  if (!conf) {
+    return DeleteError::NON_EXISTENT;
+  }
+  const auto &storage_path = conf->durability.storage_directory;
+  const auto &uuid = conf->salient.uuid;
+
+  // Check if db exists
+  try {
+    // Low level handlers
+    if (!db_handler_.TryDelete(db_name)) {
+      return DeleteError::USING;
+    }
+  } catch (utils::BasicException &) {
+    return DeleteError::NON_EXISTENT;
+  }
+
+  // Remove from durability list
+  if (durability_) durability_->Delete(Durability::GenKey(db_name));
+
+  // Delete disk storage
+  std::error_code ec;
+  (void)std::filesystem::remove_all(storage_path, ec);
+  if (ec) {
+    spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path);
+  }
+
+  // Success
+  // Save delta
+  if (system_transaction_) {
+    system_transaction_->delta.emplace(SystemTransaction::Delta::drop_database, uuid);
+  }
+  return {};
+}
+
+DbmsHandler::DeleteResult DbmsHandler::Delete(std::string_view db_name) {
+  auto wr = std::lock_guard(lock_);
+  return Delete_(db_name);
+}
+
+DbmsHandler::DeleteResult DbmsHandler::Delete(utils::UUID uuid) {
+  auto wr = std::lock_guard(lock_);
+  std::string db_name;
+  try {
+    const auto db = Get_(uuid);
+    db_name = db->name();
+  } catch (const UnknownDatabaseException &) {
+    return DeleteError::NON_EXISTENT;
+  }
+  return Delete_(db_name);
+}
+
+DbmsHandler::NewResultT DbmsHandler::New_(storage::Config storage_config) {
+  auto new_db = db_handler_.New(storage_config, repl_state_);
+
+  if (new_db.HasValue()) {  // Success
+                            // Save delta
+    if (system_transaction_) {
+      system_transaction_->delta.emplace(SystemTransaction::Delta::create_database, storage_config.salient);
+    }
+    UpdateDurability(storage_config);
+    return new_db.GetValue();
+  }
+  return new_db.GetError();
+}
+
+DbmsHandler::DeleteResult DbmsHandler::Delete_(std::string_view db_name) {
+  if (db_name == kDefaultDB) {
+    // MSG cannot delete the default db
+    return DeleteError::DEFAULT_DB;
+  }
+
+  const auto storage_path = StorageDir_(db_name);
+  if (!storage_path) return DeleteError::NON_EXISTENT;
+
+  {
+    auto db = db_handler_.Get(db_name);
+    if (!db) return DeleteError::NON_EXISTENT;
+    // TODO: ATM we assume REPLICA won't have streams,
+    //       this is a best effort approach just in case they do
+    //       there is still subtle data race we stream manipulation
+    //       can occur while we are dropping the database
+    db->prepare_for_deletion();
+    auto &database = *db->get();
+    database.streams()->StopAll();
+    database.streams()->DropAll();
+    database.thread_pool()->Shutdown();
+  }
+
+  // Remove from durability list
+  if (durability_) durability_->Delete(Durability::GenKey(db_name));
+
+  // Check if db exists
+  // Low level handlers
+  db_handler_.DeferDelete(db_name, [storage_path = *storage_path, db_name = std::string{db_name}]() {
+    // Delete disk storage
+    std::error_code ec;
+    (void)std::filesystem::remove_all(storage_path, ec);
+    if (ec) {
+      spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path);
+    }
+  });
+
+  return {};  // Success
+}
+
+void DbmsHandler::UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir) {
+  if (!durability_) return;
+  // Save database in a list of active databases
+  const auto &key = Durability::GenKey(config.salient.name);
+  if (rel_dir == std::nullopt)
+    rel_dir =
+        std::filesystem::relative(config.durability.storage_directory, default_config_.durability.storage_directory);
+  const auto &val = Durability::GenVal(config.salient.uuid, *rel_dir);
+  durability_->Put(key, val);
+}
+
+AllSyncReplicaStatus DbmsHandler::Commit() {
+  if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt)
+    return AllSyncReplicaStatus::AllCommitsConfirmed;  // Nothing to commit
+  const auto &delta = *system_transaction_->delta;
+
+  auto sync_status = AllSyncReplicaStatus::AllCommitsConfirmed;
+  // TODO Create a system client that can handle all of this automatically
+  switch (delta.action) {
+    using enum SystemTransaction::Delta::Action;
+    case CREATE_DATABASE: {
+      // Replication
+      auto main_handler = [&](memgraph::replication::RoleMainData &main_data) {
+        // TODO: data race issue? registered_replicas_ access not protected
+        // This is sync in any case, as this is the startup
+        for (auto &client : main_data.registered_replicas_) {
+          bool completed = SteamAndFinalizeDelta<storage::replication::CreateDatabaseRpc>(
+              client,
+              [](const storage::replication::CreateDatabaseRes &response) {
+                return response.result != storage::replication::CreateDatabaseRes::Result::FAILURE;
+              },
+              std::string(main_data.epoch_.id()), last_commited_system_timestamp_,
+              system_transaction_->system_timestamp, delta.config);
+          // TODO: reduce duplicate code
+          if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) {
+            sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
+          }
+        }
+        // Sync database with REPLICAs
+        RecoverReplication(Get_(delta.config.name));
+      };
+      auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ };
+      std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
+    } break;
+    case DROP_DATABASE: {
+      // Replication
+      auto main_handler = [&](memgraph::replication::RoleMainData &main_data) {
+        // TODO: data race issue? registered_replicas_ access not protected
+        // This is sync in any case, as this is the startup
+        for (auto &client : main_data.registered_replicas_) {
+          bool completed = SteamAndFinalizeDelta<storage::replication::DropDatabaseRpc>(
+              client,
+              [](const storage::replication::DropDatabaseRes &response) {
+                return response.result != storage::replication::DropDatabaseRes::Result::FAILURE;
+              },
+              std::string(main_data.epoch_.id()), last_commited_system_timestamp_,
+              system_transaction_->system_timestamp, delta.uuid);
+          // TODO: reduce duplicate code
+          if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) {
+            sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
+          }
+        }
+      };
+      auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ };
+      std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
+    } break;
+  }
+
+  durability_->Put(kLastCommitedSystemTsKey, std::to_string(system_transaction_->system_timestamp));
+  last_commited_system_timestamp_ = system_transaction_->system_timestamp;
+  ResetSystemTransaction();
+  return sync_status;
+}
+
+#else  // not MG_ENTERPRISE
+
+AllSyncReplicaStatus DbmsHandler::Commit() {
+  if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt) {
+    return AllSyncReplicaStatus::AllCommitsConfirmed;  // Nothing to commit
+  }
+  const auto &delta = *system_transaction_->delta;
+
+  switch (delta.action) {
+    using enum SystemTransaction::Delta::Action;
+    case CREATE_DATABASE:
+    case DROP_DATABASE:
+      /* Community edition doesn't support multi-tenant replication */
+      break;
+  }
+
+  last_commited_system_timestamp_ = system_transaction_->system_timestamp;
+  ResetSystemTransaction();
+  return AllSyncReplicaStatus::AllCommitsConfirmed;
+}
+
+#endif
 }  // namespace memgraph::dbms
diff --git a/src/dbms/dbms_handler.hpp b/src/dbms/dbms_handler.hpp
index 3151398ab..2066321e2 100644
--- a/src/dbms/dbms_handler.hpp
+++ b/src/dbms/dbms_handler.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,34 +12,37 @@
 #pragma once
 
 #include <algorithm>
-#include <concepts>
+#include <atomic>
 #include <cstdint>
 #include <filesystem>
 #include <memory>
 #include <mutex>
 #include <optional>
-#include <ostream>
-#include <stdexcept>
 #include <system_error>
-#include <unordered_map>
+#include <utility>
 
 #include "auth/auth.hpp"
 #include "constants.hpp"
 #include "dbms/database.hpp"
 #include "dbms/inmemory/replication_handlers.hpp"
+#include "dbms/replication_handler.hpp"
+#include "kvstore/kvstore.hpp"
+#include "replication/replication_client.hpp"
+#include "storage/v2/config.hpp"
+#include "storage/v2/replication/enums.hpp"
+#include "storage/v2/replication/rpc.hpp"
+#include "storage/v2/transaction.hpp"
+#include "utils/thread_pool.hpp"
 #ifdef MG_ENTERPRISE
+#include "coordination/coordinator_state.hpp"
 #include "dbms/database_handler.hpp"
 #endif
-#include "dbms/replication_client.hpp"
+#include "dbms/transaction.hpp"
 #include "global.hpp"
 #include "query/config.hpp"
 #include "query/interpreter_context.hpp"
 #include "spdlog/spdlog.h"
-#include "storage/v2/durability/durability.hpp"
-#include "storage/v2/durability/paths.hpp"
 #include "storage/v2/isolation_level.hpp"
-#include "utils/exceptions.hpp"
-#include "utils/file.hpp"
 #include "utils/logging.hpp"
 #include "utils/result.hpp"
 #include "utils/rw_lock.hpp"
@@ -48,6 +51,11 @@
 
 namespace memgraph::dbms {
 
+enum class AllSyncReplicaStatus {
+  AllCommitsConfirmed,
+  SomeCommitsUnconfirmed,
+};
+
 struct Statistics {
   uint64_t num_vertex;           //!< Sum of vertexes in every database
   uint64_t num_edges;            //!< Sum of edges in every database
@@ -102,11 +110,10 @@ class DbmsHandler {
    * @param configs storage configuration
    * @param auth pointer to the global authenticator
    * @param recovery_on_startup restore databases (and its content) and authentication data
-   * @param delete_on_drop when dropping delete any associated directories on disk
    */
   DbmsHandler(storage::Config config,
               memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
-              bool recovery_on_startup, bool delete_on_drop);  // TODO If more arguments are added use a config strut
+              bool recovery_on_startup);  // TODO If more arguments are added use a config struct
 #else
   /**
    * @brief Initialize the handler. A single database is supported in community edition.
@@ -116,10 +123,12 @@ class DbmsHandler {
   DbmsHandler(storage::Config config)
       : repl_state_{ReplicationStateRootPath(config)},
         db_gatekeeper_{[&] {
-                         config.name = kDefaultDB;
+                         config.salient.name = kDefaultDB;
                          return std::move(config);
                        }(),
-                       repl_state_} {}
+                       repl_state_} {
+    RecoverReplication(Get());
+  }
 #endif
 
 #ifdef MG_ENTERPRISE
@@ -131,9 +140,56 @@ class DbmsHandler {
    */
   NewResultT New(const std::string &name) {
     std::lock_guard<LockT> wr(lock_);
-    return New_(name, name);
+    const auto uuid = utils::UUID{};
+    return New_(name, uuid);
   }
 
+  /**
+   * @brief Create new if name/uuid do not match any database. Drop and recreate if database already present.
+   * @note Default database is not dropped, only its UUID is updated and only if the database is clean.
+   *
+   * @param config desired salient config
+   * @return NewResultT context on success, error on failure
+   */
+  NewResultT Update(const storage::SalientConfig &config) {
+    std::lock_guard<LockT> wr(lock_);
+    auto new_db = New_(config);
+    if (new_db.HasValue() || new_db.GetError() != NewError::EXISTS) {
+      // NOTE: If db already exists we retry below
+      return new_db;
+    }
+
+    spdlog::debug("Trying to create db '{}' on replica which already exists.", config.name);
+
+    auto db = Get_(config.name);
+    if (db->uuid() == config.uuid) {  // Same db
+      return db;
+    }
+
+    spdlog::debug("Different UUIDs");
+
+    // TODO: Fix this hack
+    if (config.name == kDefaultDB) {
+      if (db->storage()->repl_storage_state_.last_commit_timestamp_ != storage::kTimestampInitialId) {
+        spdlog::debug("Default storage is not clean, cannot update UUID...");
+        return NewError::GENERIC;  // Update error
+      }
+      spdlog::debug("Update default db's UUID");
+      // Default db cannot be deleted and remade, have to just update the UUID
+      db->storage()->config_.salient.uuid = config.uuid;
+      UpdateDurability(db->storage()->config_, ".");
+      return db;
+    }
+
+    spdlog::debug("Drop database and recreate with the correct UUID");
+    // Defer drop
+    (void)Delete_(db->name());
+    // Second attempt
+    return New_(config);
+  }
+
+  void UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir = {});
+
   /**
    * @brief Get the context associated with the "name" database
    *
@@ -145,6 +201,19 @@ class DbmsHandler {
     std::shared_lock<LockT> rd(lock_);
     return Get_(name);
   }
+
+  /**
+   * @brief Get the context associated with the UUID database
+   *
+   * @param uuid
+   * @return DatabaseAccess
+   * @throw UnknownDatabaseException if database not found
+   */
+  DatabaseAccess Get(const utils::UUID &uuid) {
+    std::shared_lock<LockT> rd(lock_);
+    return Get_(uuid);
+  }
+
 #else
   /**
    * @brief Get the context associated with the default database
@@ -160,50 +229,28 @@ class DbmsHandler {
 
 #ifdef MG_ENTERPRISE
   /**
-   * @brief Delete database.
+   * @brief Attempt to delete database.
    *
    * @param db_name database name
    * @return DeleteResult error on failure
    */
-  DeleteResult Delete(const std::string &db_name) {
-    std::lock_guard<LockT> wr(lock_);
-    if (db_name == kDefaultDB) {
-      // MSG cannot delete the default db
-      return DeleteError::DEFAULT_DB;
-    }
+  DeleteResult TryDelete(std::string_view db_name);
 
-    const auto storage_path = StorageDir_(db_name);
-    if (!storage_path) return DeleteError::NON_EXISTENT;
+  /**
+   * @brief Delete or defer deletion of database.
+   *
+   * @param db_name database name
+   * @return DeleteResult error on failure
+   */
+  DeleteResult Delete(std::string_view db_name);
 
-    // Check if db exists
-    try {
-      // Low level handlers
-      if (!db_handler_.Delete(db_name)) {
-        return DeleteError::USING;
-      }
-    } catch (utils::BasicException &) {
-      return DeleteError::NON_EXISTENT;
-    }
-
-    // Remove from durability list
-    if (durability_) durability_->Delete(db_name);
-
-    // Delete disk storage
-    if (delete_on_drop_) {
-      std::error_code ec;
-      (void)std::filesystem::remove_all(*storage_path, ec);
-      if (ec) {
-        spdlog::error("Failed to clean disk while deleting database \"{}\".", db_name);
-        defunct_dbs_.emplace(db_name);
-        return DeleteError::DISK_FAIL;
-      }
-    }
-
-    // Delete from defunct_dbs_ (in case a second delete call was successful)
-    defunct_dbs_.erase(db_name);
-
-    return {};  // Success
-  }
+  /**
+   * @brief Delete or defer deletion of database.
+   *
+   * @param uuid database UUID
+   * @return DeleteResult error on failure
+   */
+  DeleteResult Delete(utils::UUID uuid);
 #endif
 
   /**
@@ -216,7 +263,7 @@ class DbmsHandler {
     std::shared_lock<LockT> rd(lock_);
     return db_handler_.All();
 #else
-    return {db_gatekeeper_.access()->get()->id()};
+    return {db_gatekeeper_.access()->get()->name()};
 #endif
   }
 
@@ -226,12 +273,17 @@ class DbmsHandler {
   bool IsMain() const { return repl_state_.IsMain(); }
   bool IsReplica() const { return repl_state_.IsReplica(); }
 
+#ifdef MG_ENTERPRISE
+  coordination::CoordinatorState &CoordinatorState() { return coordinator_state_; }
+#endif
+
   /**
    * @brief Return the statistics all databases.
    *
    * @return Statistics
    */
   Statistics Stats() {
+    auto const replication_role = repl_state_.GetRole();
     Statistics stats{};
     // TODO: Handle overflow?
 #ifdef MG_ENTERPRISE
@@ -244,7 +296,7 @@ class DbmsHandler {
       auto db_acc_opt = db_gk.access();
       if (db_acc_opt) {
         auto &db_acc = *db_acc_opt;
-        const auto &info = db_acc->GetInfo();
+        const auto &info = db_acc->GetInfo(false, replication_role);
         const auto &storage_info = info.storage_info;
         stats.num_vertex += storage_info.vertex_count;
         stats.num_edges += storage_info.edge_count;
@@ -268,6 +320,7 @@ class DbmsHandler {
    * @return std::vector<DatabaseInfo>
    */
   std::vector<DatabaseInfo> Info() {
+    auto const replication_role = repl_state_.GetRole();
     std::vector<DatabaseInfo> res;
 #ifdef MG_ENTERPRISE
     std::shared_lock<LockT> rd(lock_);
@@ -280,7 +333,7 @@ class DbmsHandler {
       auto db_acc_opt = db_gk.access();
       if (db_acc_opt) {
         auto &db_acc = *db_acc_opt;
-        res.push_back(db_acc->GetInfo());
+        res.push_back(db_acc->GetInfo(false, replication_role));
       }
     }
     return res;
@@ -303,7 +356,7 @@ class DbmsHandler {
       auto db_acc_opt = db_gk.access();
       if (db_acc_opt) {
         auto &db_acc = *db_acc_opt;
-        spdlog::debug("Restoring trigger for database \"{}\"", db_acc->id());
+        spdlog::debug("Restoring trigger for database \"{}\"", db_acc->name());
         auto storage_accessor = db_acc->Access();
         auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
         db_acc->trigger_store()->RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker);
@@ -328,7 +381,7 @@ class DbmsHandler {
       auto db_acc = db_gk.access();
       if (db_acc) {
         auto *db = db_acc->get();
-        spdlog::debug("Restoring streams for database \"{}\"", db->id());
+        spdlog::debug("Restoring streams for database \"{}\"", db->name());
         db->streams()->RestoreStreams(*db_acc, ic);
       }
     }
@@ -339,7 +392,7 @@ class DbmsHandler {
    *
    * @param f
    */
-  void ForEach(auto f) {
+  void ForEach(std::invocable<DatabaseAccess> auto f) {
 #ifdef MG_ENTERPRISE
     std::shared_lock<LockT> rd(lock_);
     for (auto &[_, db_gk] : db_handler_) {
@@ -349,33 +402,103 @@ class DbmsHandler {
 #endif
       auto db_acc = db_gk.access();
       if (db_acc) {  // This isn't an error, just a defunct db
-        f(db_acc->get());
+        f(*db_acc);
       }
     }
   }
 
-  /**
-   * @brief todo
-   *
-   * @param f
-   */
-  void ForOne(auto f) {
+  void NewSystemTransaction() {
+    DMG_ASSERT(!system_transaction_, "Already running a system transaction");
+    system_transaction_.emplace(++system_timestamp_);
+  }
+
+  void ResetSystemTransaction() { system_transaction_.reset(); }
+
+  //! \tparam RPC An rpc::RequestResponse
+  //! \tparam Args the args type
+  //! \param client the client to use for rpc communication
+  //! \param check predicate to check response is ok
+  //! \param args arguments to forward to the rpc request
+  //! \return If replica stream is completed or enqueued
+  template <typename RPC, typename... Args>
+  bool SteamAndFinalizeDelta(auto &client, auto &&check, Args &&...args) {
+    try {
+      auto stream = client.rpc_client_.template Stream<RPC>(std::forward<Args>(args)...);
+      auto task = [&client, check = std::forward<decltype(check)>(check), stream = std::move(stream)]() mutable {
+        if (stream.IsDefunct()) {
+          client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
+          return false;
+        }
+        try {
+          if (check(stream.AwaitResponse())) {
+            return true;
+          }
+        } catch (memgraph::rpc::GenericRpcFailedException const &e) {
+          // swallow error, fallthrough to error handling
+        }
+        // This replica needs SYSTEM recovery
+        client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
+        return false;
+      };
+
+      if (client.mode_ == memgraph::replication_coordination_glue::ReplicationMode::ASYNC) {
+        client.thread_pool_.AddTask([task = utils::CopyMovableFunctionWrapper{std::move(task)}]() mutable { task(); });
+        return true;
+      }
+
+      return task();
+    } catch (memgraph::rpc::GenericRpcFailedException const &e) {
+      // This replica needs SYSTEM recovery
+      client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
+      return false;
+    }
+  };
+
+  AllSyncReplicaStatus Commit();
+
+  auto LastCommitedTS() const -> uint64_t { return last_commited_system_timestamp_; }
+  void SetLastCommitedTS(uint64_t new_ts) { last_commited_system_timestamp_.store(new_ts); }
+
 #ifdef MG_ENTERPRISE
-    std::shared_lock<LockT> rd(lock_);
-    for (auto &[_, db_gk] : db_handler_) {
-      auto db_acc = db_gk.access();
-      if (db_acc) {                   // This isn't an error, just a defunct db
-        if (f(db_acc->get())) break;  // Run until the first successful one
+  // When being called by intepreter no need to gain lock, it should already be under a system transaction
+  // But concurrently the FrequentCheck is running and will need to lock before reading last_commited_system_timestamp_
+  template <bool REQUIRE_LOCK = false>
+  void SystemRestore(replication::ReplicationClient &client) {
+    // Check if system is up to date
+    if (client.state_.WithLock(
+            [](auto &state) { return state == memgraph::replication::ReplicationClient::State::READY; }))
+      return;
+
+    // Try to recover...
+    {
+      auto [database_configs, last_commited_system_timestamp] = std::invoke([&] {
+        auto sys_guard =
+            std::unique_lock{system_lock_, std::defer_lock};  // ensure no other system transaction in progress
+        if constexpr (REQUIRE_LOCK) {
+          sys_guard.lock();
+        }
+        auto configs = std::vector<storage::SalientConfig>{};
+        ForEach([&configs](DatabaseAccess acc) { configs.emplace_back(acc->config().salient); });
+        return std::pair{configs, last_commited_system_timestamp_.load()};
+      });
+      try {
+        auto stream = client.rpc_client_.Stream<storage::replication::SystemRecoveryRpc>(last_commited_system_timestamp,
+                                                                                         std::move(database_configs));
+        const auto response = stream.AwaitResponse();
+        if (response.result == storage::replication::SystemRecoveryRes::Result::FAILURE) {
+          client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
+          return;
+        }
+      } catch (memgraph::rpc::GenericRpcFailedException const &e) {
+        client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
+        return;
       }
     }
-#else
-    {
-      auto db_acc = db_gatekeeper_.access();
-      MG_ASSERT(db_acc, "Should always have the database");
-      f(db_acc->get());
-    }
-#endif
+
+    // Successfully recovered
+    client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::READY; });
   }
+#endif
 
  private:
 #ifdef MG_ENTERPRISE
@@ -385,7 +508,7 @@ class DbmsHandler {
    * @param name Database name
    * @return std::optional<std::filesystem::path>
    */
-  std::optional<std::filesystem::path> StorageDir_(const std::string &name) {
+  std::optional<std::filesystem::path> StorageDir_(std::string_view name) {
     const auto conf = db_handler_.GetConfig(name);
     if (conf) {
       return conf->durability.storage_directory;
@@ -398,105 +521,108 @@ class DbmsHandler {
    * @brief Create a new Database associated with the "name" database
    *
    * @param name name of the database
+   * @param uuid undelying RocksDB directory
    * @return NewResultT context on success, error on failure
    */
-  NewResultT New_(const std::string &name) { return New_(name, name); }
+  NewResultT New_(std::string_view name, utils::UUID uuid, std::optional<std::filesystem::path> rel_dir = {}) {
+    auto config_copy = default_config_;
+    config_copy.salient.name = name;
+    config_copy.salient.uuid = uuid;
+    spdlog::debug("Creating database '{}' - '{}'", name, std::string{uuid});
+    if (rel_dir) {
+      storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / *rel_dir);
+    } else {
+      storage::UpdatePaths(config_copy,
+                           default_config_.durability.storage_directory / kMultiTenantDir / std::string{uuid});
+    }
+    return New_(std::move(config_copy));
+  }
 
   /**
-   * @brief Create a new Database associated with the "name" database
+   * @brief Create a new Database using the passed configuration
    *
-   * @param name name of the database
-   * @param storage_subdir undelying RocksDB directory
+   * @param config configuration to be used
    * @return NewResultT context on success, error on failure
    */
-  NewResultT New_(const std::string &name, std::filesystem::path storage_subdir) {
+  NewResultT New_(const storage::SalientConfig &config) {
     auto config_copy = default_config_;
-    storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / storage_subdir);
-    return New_(name, config_copy);
+    config_copy.salient = config;  // name, uuid, mode, etc
+    UpdatePaths(config_copy, config_copy.durability.storage_directory / kMultiTenantDir / std::string{config.uuid});
+    return New_(std::move(config_copy));
   }
 
   /**
    * @brief Create a new Database associated with the "name" database
    *
-   * @param name name of the database
    * @param storage_config storage configuration
    * @return NewResultT context on success, error on failure
    */
-  NewResultT New_(const std::string &name, storage::Config &storage_config) {
-    if (defunct_dbs_.contains(name)) {
-      spdlog::warn("Failed to generate database due to the unknown state of the previously defunct database \"{}\".",
-                   name);
-      return NewError::DEFUNCT;
-    }
+  NewResultT New_(storage::Config storage_config);
 
-    auto new_db = db_handler_.New(name, storage_config, repl_state_);
-    if (new_db.HasValue()) {
-      // Success
-      if (durability_) durability_->Put(name, "ok");  // TODO: Serialize the configuration?
-      return new_db.GetValue();
-    }
-    return new_db.GetError();
-  }
+  // TODO: new overload of Delete_ with DatabaseAccess
+  DeleteResult Delete_(std::string_view db_name);
 
   /**
    * @brief Create a new Database associated with the default database
    *
    * @return NewResultT context on success, error on failure
    */
-  NewResultT NewDefault_() {
-    // Create the default DB in the root (this is how it was done pre multi-tenancy)
-    auto res = New_(kDefaultDB, "..");
-    if (res.HasValue()) {
-      // For back-compatibility...
-      // Recreate the dbms layout for the default db and symlink to the root
-      const auto dir = StorageDir_(kDefaultDB);
-      MG_ASSERT(dir, "Failed to find storage path.");
-      const auto main_dir = *dir / "databases" / kDefaultDB;
+  void SetupDefault_() {
+    try {
+      Get(kDefaultDB);
+    } catch (const UnknownDatabaseException &) {
+      // No default DB restored, create it
+      MG_ASSERT(New_(kDefaultDB, {/* random UUID */}, ".").HasValue(), "Failed while creating the default database");
+    }
 
-      if (!std::filesystem::exists(main_dir)) {
-        std::filesystem::create_directory(main_dir);
-      }
+    // For back-compatibility...
+    // Recreate the dbms layout for the default db and symlink to the root
+    const auto dir = StorageDir_(kDefaultDB);
+    MG_ASSERT(dir, "Failed to find storage path.");
+    const auto main_dir = *dir / kMultiTenantDir / kDefaultDB;
 
-      // Force link on-disk directories
-      const auto conf = db_handler_.GetConfig(kDefaultDB);
-      MG_ASSERT(conf, "No configuration for the default database.");
-      const auto &tmp_conf = conf->disk;
-      std::vector<std::filesystem::path> to_link{
-          tmp_conf.main_storage_directory,         tmp_conf.label_index_directory,
-          tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
-          tmp_conf.name_id_mapper_directory,       tmp_conf.id_name_mapper_directory,
-          tmp_conf.durability_directory,           tmp_conf.wal_directory,
-      };
+    if (!std::filesystem::exists(main_dir)) {
+      std::filesystem::create_directory(main_dir);
+    }
 
-      // Add in-memory paths
-      // Some directories are redundant (skip those)
-      const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
-      for (auto const &item : std::filesystem::directory_iterator{*dir}) {
-        const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
-        if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
-        to_link.push_back(item.path());
-      }
+    // Force link on-disk directories
+    const auto conf = db_handler_.GetConfig(kDefaultDB);
+    MG_ASSERT(conf, "No configuration for the default database.");
+    const auto &tmp_conf = conf->disk;
+    std::vector<std::filesystem::path> to_link{
+        tmp_conf.main_storage_directory,         tmp_conf.label_index_directory,
+        tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
+        tmp_conf.name_id_mapper_directory,       tmp_conf.id_name_mapper_directory,
+        tmp_conf.durability_directory,           tmp_conf.wal_directory,
+    };
 
-      // Symlink to root dir
-      for (auto const &item : to_link) {
-        const auto dir_name = std::filesystem::relative(item, item.parent_path());
-        const auto link = main_dir / dir_name;
-        const auto to = std::filesystem::relative(item, main_dir);
-        if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
-          std::filesystem::create_directory_symlink(to, link);
-        } else {  // Check existing link
-          std::error_code ec;
-          const auto test_link = std::filesystem::read_symlink(link, ec);
-          if (ec || test_link != to) {
-            MG_ASSERT(false,
-                      "Memgraph storage directory incompatible with new version.\n"
-                      "Please use a clean directory or remove \"{}\" and try again.",
-                      link.string());
-          }
+    // Add in-memory paths
+    // Some directories are redundant (skip those)
+    const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
+    for (auto const &item : std::filesystem::directory_iterator{*dir}) {
+      const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
+      if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
+      to_link.push_back(item.path());
+    }
+
+    // Symlink to root dir
+    for (auto const &item : to_link) {
+      const auto dir_name = std::filesystem::relative(item, item.parent_path());
+      const auto link = main_dir / dir_name;
+      const auto to = std::filesystem::relative(item, main_dir);
+      if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
+        std::filesystem::create_directory_symlink(to, link);
+      } else {  // Check existing link
+        std::error_code ec;
+        const auto test_link = std::filesystem::read_symlink(link, ec);
+        if (ec || test_link != to) {
+          MG_ASSERT(false,
+                    "Memgraph storage directory incompatible with new version.\n"
+                    "Please use a clean directory or remove \"{}\" and try again.",
+                    link.string());
         }
       }
     }
-    return res;
   }
 
   /**
@@ -514,17 +640,57 @@ class DbmsHandler {
     throw UnknownDatabaseException("Tried to retrieve an unknown database \"{}\".", name);
   }
 
+  /**
+   * @brief Get the context associated with the UUID database
+   *
+   * @param uuid
+   * @return DatabaseAccess
+   * @throw UnknownDatabaseException if database not found
+   */
+  DatabaseAccess Get_(const utils::UUID &uuid) {
+    // TODO Speed up
+    for (auto &[_, db_gk] : db_handler_) {
+      auto acc = db_gk.access();
+      if (acc->get()->uuid() == uuid) {
+        return std::move(*acc);
+      }
+    }
+    throw UnknownDatabaseException("Tried to retrieve an unknown database with UUID \"{}\".", std::string{uuid});
+  }
+#endif
+
+  void RecoverReplication(DatabaseAccess db_acc) {
+    if (allow_mt_repl || db_acc->name() == dbms::kDefaultDB) {
+      // Handle global replication state
+      spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
+      // RECOVER REPLICA CONNECTIONS
+      memgraph::dbms::RestoreReplication(repl_state_, std::move(db_acc));
+    } else if (const ::memgraph::replication::RoleMainData *data =
+                   std::get_if<::memgraph::replication::RoleMainData>(&repl_state_.ReplicationData());
+               data && !data->registered_replicas_.empty()) {
+      spdlog::warn("Multi-tenant replication is currently not supported!");
+    }
+  }
+
+#ifdef MG_ENTERPRISE
   mutable LockT lock_{utils::RWLock::Priority::READ};  //!< protective lock
   storage::Config default_config_;                     //!< Storage configuration used when creating new databases
   DatabaseHandler db_handler_;                         //!< multi-tenancy storage handler
   std::unique_ptr<kvstore::KVStore> durability_;       //!< list of active dbs (pointer so we can postpone its creation)
-  bool delete_on_drop_;                                //!< Flag defining if dropping storage also deletes its directory
-  std::set<std::string> defunct_dbs_;  //!< Databases that are in an unknown state due to various failures
+  coordination::CoordinatorState coordinator_state_;  //!< Replication coordinator
 #endif
+  // TODO: Make an api
+ public:
+  utils::ResourceLock system_lock_{};  //!> Ensure exclusive access for system queries
+ private:
+  std::optional<SystemTransaction> system_transaction_;      //!< Current system transaction (only one at a time)
+  uint64_t system_timestamp_{storage::kTimestampInitialId};  //!< System timestamp
+  std::atomic_uint64_t last_commited_system_timestamp_{
+      storage::kTimestampInitialId};          //!< Last commited system timestamp
   replication::ReplicationState repl_state_;  //!< Global replication state
 #ifndef MG_ENTERPRISE
   mutable utils::Gatekeeper<Database> db_gatekeeper_;  //!< Single databases gatekeeper
 #endif
-};
+};  // namespace memgraph::dbms
 
 }  // namespace memgraph::dbms
diff --git a/src/dbms/handler.hpp b/src/dbms/handler.hpp
index 568b2fc7c..53724dabe 100644
--- a/src/dbms/handler.hpp
+++ b/src/dbms/handler.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -21,6 +21,7 @@
 #include "utils/exceptions.hpp"
 #include "utils/gatekeeper.hpp"
 #include "utils/result.hpp"
+#include "utils/thread_pool.hpp"
 
 namespace memgraph::dbms {
 
@@ -82,7 +83,7 @@ class Handler {
    * @return true on success
    * @throw BasicException
    */
-  bool Delete(const std::string &name) {
+  bool TryDelete(std::string_view name) {
     if (auto itr = items_.find(name); itr != items_.end()) {
       auto db_acc = itr->second.access();
       if (db_acc && db_acc->try_delete()) {
@@ -92,9 +93,42 @@ class Handler {
       }
       return false;
     }
+    // TODO: Change to return enum
     throw utils::BasicException("Unknown item \"{}\".", name);
   }
 
+  /**
+   * @brief Delete or defunct the context associated with the name.
+   *
+   * @param name Name associated with the context to delete
+   * @param post_delete_func What to do after deletion has happened
+   */
+  template <typename Func>
+  void DeferDelete(std::string_view name, Func &&post_delete_func) {
+    auto itr = items_.find(name);
+    if (itr == items_.end()) return;
+
+    auto db_acc = itr->second.access();
+    if (!db_acc) return;
+
+    if (db_acc->try_delete()) {
+      // Delete the database now
+      db_acc->reset();
+      post_delete_func();
+    } else {
+      // Defer deletion
+      db_acc->reset();
+      // TODO: Make sure this shuts down correctly
+      auto task = [gk = std::move(itr->second), post_delete_func = std::forward<Func>(post_delete_func)]() mutable {
+        gk.~Gatekeeper<T>();
+        post_delete_func();
+      };
+      defer_pool_.AddTask(utils::CopyMovableFunctionWrapper{std::move(task)});
+    }
+    // In any case remove from handled map
+    items_.erase(itr);
+  }
+
   /**
    * @brief Check if a name is already used.
    *
@@ -120,6 +154,7 @@ class Handler {
  private:
   std::unordered_map<std::string, utils::Gatekeeper<T>, string_hash, std::equal_to<>>
       items_;  //!< map to all active items
+  utils::ThreadPool defer_pool_{1};
 };
 
 }  // namespace memgraph::dbms
diff --git a/src/dbms/inmemory/replication_handlers.cpp b/src/dbms/inmemory/replication_handlers.cpp
index 5eba61878..cef2bf8c6 100644
--- a/src/dbms/inmemory/replication_handlers.cpp
+++ b/src/dbms/inmemory/replication_handlers.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,6 +10,7 @@
 // licenses/APL.txt.
 
 #include "dbms/inmemory/replication_handlers.hpp"
+#include <chrono>
 #include <optional>
 #include "dbms/constants.hpp"
 #include "dbms/dbms_handler.hpp"
@@ -22,6 +23,7 @@
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/inmemory/unique_constraints.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using memgraph::storage::Delta;
 using memgraph::storage::EdgeAccessor;
 using memgraph::storage::EdgeRef;
@@ -48,29 +50,29 @@ std::pair<uint64_t, WalDeltaData> ReadDelta(storage::durability::BaseDecoder *de
   }
 };
 
-std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, std::string_view db_name) {
+std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, const utils::UUID &uuid) {
   try {
 #ifdef MG_ENTERPRISE
-    auto acc = dbms_handler->Get(db_name);
-#else
-    if (db_name != dbms::kDefaultDB) {
-      spdlog::warn("Trying to replicate a non-default database on a community replica.");
-      return std::nullopt;
-    }
-    auto acc = dbms_handler->Get();
-#endif
+    auto acc = dbms_handler->Get(uuid);
     if (!acc) {
-      spdlog::error("Failed to get access to ", db_name);
+      spdlog::error("Failed to get access to UUID ", std::string{uuid});
       return std::nullopt;
     }
+#else
+    auto acc = dbms_handler->Get();
+    if (!acc) {
+      spdlog::warn("Failed to get access to the default db.");
+      return std::nullopt;
+    }
+#endif
     auto *inmem_storage = dynamic_cast<storage::InMemoryStorage *>(acc.get()->storage());
     if (!inmem_storage || inmem_storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
-      spdlog::error("Database \"{}\" is not IN_MEMORY_TRANSACTIONAL.", db_name);
+      spdlog::error("Database is not IN_MEMORY_TRANSACTIONAL.");
       return std::nullopt;
     }
     return std::optional{std::move(acc)};
   } catch (const dbms::UnknownDatabaseException &e) {
-    spdlog::warn("No database \"{}\" on replica!", db_name);
+    spdlog::warn("No database with UUID \"{}\" on replica!", std::string{uuid});
     return std::nullopt;
   }
 }
@@ -108,13 +110,16 @@ void InMemoryReplicationHandlers::HeartbeatHandler(dbms::DbmsHandler *dbms_handl
                                                    slk::Builder *res_builder) {
   storage::replication::HeartbeatReq req;
   slk::Load(&req, req_reader);
-  auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
-  if (!db_acc) return;
+  auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
+  if (!db_acc) {
+    storage::replication::HeartbeatRes res{false, 0, ""};
+    slk::Save(res, res_builder);
+    return;
+  }
 
   // TODO: this handler is agnostic of InMemory, move to be reused by on-disk
   auto const *storage = db_acc->get()->storage();
-  storage::replication::HeartbeatRes res{storage->id(), true,
-                                         storage->repl_storage_state_.last_commit_timestamp_.load(),
+  storage::replication::HeartbeatRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load(),
                                          std::string{storage->repl_storage_state_.epoch_.id()}};
   slk::Save(res, res_builder);
 }
@@ -123,8 +128,12 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
                                                       slk::Builder *res_builder) {
   storage::replication::AppendDeltasReq req;
   slk::Load(&req, req_reader);
-  auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
-  if (!db_acc) return;
+  auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
+  if (!db_acc) {
+    storage::replication::AppendDeltasRes res{false, 0};
+    slk::Save(res, res_builder);
+    return;
+  }
 
   storage::replication::Decoder decoder(req_reader);
 
@@ -164,7 +173,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
           storage::durability::kVersion);  // TODO: Check if we are always using the latest version when replicating
     }
 
-    storage::replication::AppendDeltasRes res{storage->id(), false, repl_storage_state.last_commit_timestamp_.load()};
+    storage::replication::AppendDeltasRes res{false, repl_storage_state.last_commit_timestamp_.load()};
     slk::Save(res, res_builder);
     return;
   }
@@ -173,7 +182,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
       storage, &decoder,
       storage::durability::kVersion);  // TODO: Check if we are always using the latest version when replicating
 
-  storage::replication::AppendDeltasRes res{storage->id(), true, repl_storage_state.last_commit_timestamp_.load()};
+  storage::replication::AppendDeltasRes res{true, repl_storage_state.last_commit_timestamp_.load()};
   slk::Save(res, res_builder);
   spdlog::debug("Replication recovery from append deltas finished, replica is now up to date!");
 }
@@ -182,8 +191,12 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
                                                   slk::Builder *res_builder) {
   storage::replication::SnapshotReq req;
   slk::Load(&req, req_reader);
-  auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
-  if (!db_acc) return;
+  auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
+  if (!db_acc) {
+    storage::replication::SnapshotRes res{false, 0};
+    slk::Save(res, res_builder);
+    return;
+  }
 
   storage::replication::Decoder decoder(req_reader);
 
@@ -231,8 +244,7 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
   }
   storage_guard.unlock();
 
-  storage::replication::SnapshotRes res{storage->id(), true,
-                                        storage->repl_storage_state_.last_commit_timestamp_.load()};
+  storage::replication::SnapshotRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
   slk::Save(res, res_builder);
 
   spdlog::trace("Deleting old snapshot files due to snapshot recovery.");
@@ -262,8 +274,12 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle
                                                   slk::Builder *res_builder) {
   storage::replication::WalFilesReq req;
   slk::Load(&req, req_reader);
-  auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
-  if (!db_acc) return;
+  auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
+  if (!db_acc) {
+    storage::replication::WalFilesRes res{false, 0};
+    slk::Save(res, res_builder);
+    return;
+  }
 
   const auto wal_file_number = req.file_number;
   spdlog::debug("Received WAL files: {}", wal_file_number);
@@ -277,8 +293,7 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle
     LoadWal(storage, &decoder);
   }
 
-  storage::replication::WalFilesRes res{storage->id(), true,
-                                        storage->repl_storage_state_.last_commit_timestamp_.load()};
+  storage::replication::WalFilesRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
   slk::Save(res, res_builder);
   spdlog::debug("Replication recovery from WAL files ended successfully, replica is now up to date!");
 }
@@ -287,8 +302,12 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand
                                                     slk::Builder *res_builder) {
   storage::replication::CurrentWalReq req;
   slk::Load(&req, req_reader);
-  auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
-  if (!db_acc) return;
+  auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
+  if (!db_acc) {
+    storage::replication::CurrentWalRes res{false, 0};
+    slk::Save(res, res_builder);
+    return;
+  }
 
   storage::replication::Decoder decoder(req_reader);
 
@@ -297,8 +316,7 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand
 
   LoadWal(storage, &decoder);
 
-  storage::replication::CurrentWalRes res{storage->id(), true,
-                                          storage->repl_storage_state_.last_commit_timestamp_.load()};
+  storage::replication::CurrentWalRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
   slk::Save(res, res_builder);
   spdlog::debug("Replication recovery from current WAL ended successfully, replica is now up to date!");
 }
@@ -317,6 +335,8 @@ void InMemoryReplicationHandlers::LoadWal(storage::InMemoryStorage *storage, sto
     }
     auto &replica_epoch = storage->repl_storage_state_.epoch_;
     if (wal_info.epoch_id != replica_epoch.id()) {
+      // questionable behaviour, we trust that any change in epoch implies change in who is MAIN
+      // when we use high availability, this assumption need to be checked.
       auto prev_epoch = replica_epoch.SetEpoch(wal_info.epoch_id);
       storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch);
     }
@@ -354,13 +374,16 @@ void InMemoryReplicationHandlers::TimestampHandler(dbms::DbmsHandler *dbms_handl
                                                    slk::Builder *res_builder) {
   storage::replication::TimestampReq req;
   slk::Load(&req, req_reader);
-  auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
-  if (!db_acc) return;
+  auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
+  if (!db_acc) {
+    storage::replication::TimestampRes res{false, 0};
+    slk::Save(res, res_builder);
+    return;
+  }
 
   // TODO: this handler is agnostic of InMemory, move to be reused by on-disk
   auto const *storage = db_acc->get()->storage();
-  storage::replication::TimestampRes res{storage->id(), true,
-                                         storage->repl_storage_state_.last_commit_timestamp_.load()};
+  storage::replication::TimestampRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
   slk::Save(res, res_builder);
 }
 
@@ -380,9 +403,9 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
     if (!commit_timestamp_and_accessor) {
       std::unique_ptr<storage::Storage::Accessor> acc = nullptr;
       if (unique) {
-        acc = storage->UniqueAccess(std::nullopt, false /*not main*/);
+        acc = storage->UniqueAccess(ReplicationRole::REPLICA);
       } else {
-        acc = storage->Access(std::nullopt, false /*not main*/);
+        acc = storage->Access(ReplicationRole::REPLICA);
       }
       auto inmem_acc = std::unique_ptr<storage::InMemoryStorage::InMemoryAccessor>(
           static_cast<storage::InMemoryStorage::InMemoryAccessor *>(acc.release()));
@@ -504,7 +527,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
       case WalDeltaData::Type::EDGE_SET_PROPERTY: {
         spdlog::trace("       Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
                       delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
-        if (!storage->config_.items.properties_on_edges)
+        if (!storage->config_.salient.items.properties_on_edges)
           throw utils::BasicException(
               "Can't set properties on edges because properties on edges "
               "are disabled!");
@@ -571,8 +594,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
         spdlog::trace("       Transaction end");
         if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
           throw utils::BasicException("Invalid commit data!");
-        auto ret =
-            commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first, false /* not main */);
+        auto ret = commit_timestamp_and_accessor->second.Commit(
+            {.desired_commit_timestamp = commit_timestamp_and_accessor->first, .is_main = false});
         if (ret.HasError())
           throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
         commit_timestamp_and_accessor = std::nullopt;
diff --git a/src/dbms/inmemory/replication_handlers.hpp b/src/dbms/inmemory/replication_handlers.hpp
index fc76d2b3a..4f6523747 100644
--- a/src/dbms/inmemory/replication_handlers.hpp
+++ b/src/dbms/inmemory/replication_handlers.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,7 +12,6 @@
 #pragma once
 
 #include "replication/replication_server.hpp"
-#include "replication/state.hpp"
 #include "storage/v2/replication/serialization.hpp"
 
 namespace memgraph::storage {
diff --git a/src/dbms/inmemory/storage_helper.hpp b/src/dbms/inmemory/storage_helper.hpp
index 1cd9f9f4e..fa1b9646a 100644
--- a/src/dbms/inmemory/storage_helper.hpp
+++ b/src/dbms/inmemory/storage_helper.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -24,37 +24,15 @@ namespace memgraph::dbms {
 
 inline std::unique_ptr<storage::Storage> CreateInMemoryStorage(storage::Config config,
                                                                ::memgraph::replication::ReplicationState &repl_state) {
-  const auto wal_mode = config.durability.snapshot_wal_mode;
-  const auto name = config.name;
+  const auto name = config.salient.name;
   auto storage = std::make_unique<storage::InMemoryStorage>(std::move(config));
 
   // Connect replication state and storage
   storage->CreateSnapshotHandler(
       [storage = storage.get(), &repl_state]() -> utils::BasicResult<storage::InMemoryStorage::CreateSnapshotError> {
-        if (repl_state.IsReplica()) {
-          return storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica;
-        }
-        return storage->CreateSnapshot();
+        return storage->CreateSnapshot(repl_state.GetRole());
       });
 
-  if (allow_mt_repl || name == dbms::kDefaultDB) {
-    // Handle global replication state
-    spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
-    // RECOVER REPLICA CONNECTIONS
-    memgraph::dbms::RestoreReplication(repl_state, *storage);
-  } else if (const ::memgraph::replication::RoleMainData *data =
-                 std::get_if<::memgraph::replication::RoleMainData>(&repl_state.ReplicationData());
-             data && !data->registered_replicas_.empty()) {
-    spdlog::warn("Multi-tenant replication is currently not supported!");
-  }
-
-  if (wal_mode == storage::Config::Durability::SnapshotWalMode::DISABLED && repl_state.IsMain()) {
-    spdlog::warn(
-        "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider "
-        "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
-        "without write-ahead logs this instance is not replicating any data.");
-  }
-
   return std::move(storage);
 }
 
diff --git a/src/dbms/replication_client.cpp b/src/dbms/replication_client.cpp
index bfa4c622f..fa0c30daa 100644
--- a/src/dbms/replication_client.cpp
+++ b/src/dbms/replication_client.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,6 +10,7 @@
 // licenses/APL.txt.
 
 #include "dbms/replication_client.hpp"
+#include "replication/replication_client.hpp"
 
 namespace memgraph::dbms {
 
@@ -17,18 +18,26 @@ void StartReplicaClient(DbmsHandler &dbms_handler, replication::ReplicationClien
   // No client error, start instance level client
   auto const &endpoint = client.rpc_client_.Endpoint();
   spdlog::trace("Replication client started at: {}:{}", endpoint.address, endpoint.port);
-  client.StartFrequentCheck([&dbms_handler](std::string_view name) {
-    // Working connection, check if any database has been left behind
-    dbms_handler.ForEach([name](dbms::Database *db) {
+  client.StartFrequentCheck([&dbms_handler](bool reconnect, replication::ReplicationClient &client) {
+    // Working connection
+    // Check if system needs restoration
+    if (reconnect) {
+      client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
+    }
+#ifdef MG_ENTERPRISE
+    dbms_handler.SystemRestore<true>(client);
+#endif
+    // Check if any database has been left behind
+    dbms_handler.ForEach([&name = client.name_, reconnect](dbms::DatabaseAccess db_acc) {
       // Specific database <-> replica client
-      db->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) {
-        if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
+      db_acc->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) {
+        if (reconnect || client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
           // Database <-> replica might be behind, check and recover
-          client->TryCheckReplicaStateAsync(db->storage());
+          client->TryCheckReplicaStateAsync(db_acc->storage(), db_acc);
         }
       });
     });
   });
-}
+}  // namespace memgraph::dbms
 
 }  // namespace memgraph::dbms
diff --git a/src/dbms/replication_handler.cpp b/src/dbms/replication_handler.cpp
index 2cbe2c432..285752f76 100644
--- a/src/dbms/replication_handler.cpp
+++ b/src/dbms/replication_handler.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,15 +11,21 @@
 
 #include "dbms/replication_handler.hpp"
 
+#include <algorithm>
+
 #include "dbms/constants.hpp"
 #include "dbms/dbms_handler.hpp"
+#include "dbms/global.hpp"
 #include "dbms/inmemory/replication_handlers.hpp"
-#include "dbms/inmemory/storage_helper.hpp"
 #include "dbms/replication_client.hpp"
+#include "dbms/utils.hpp"
+#include "replication/messages.hpp"
 #include "replication/state.hpp"
+#include "spdlog/spdlog.h"
+#include "storage/v2/config.hpp"
+#include "storage/v2/replication/rpc.hpp"
+#include "utils/on_scope_exit.hpp"
 
-using memgraph::replication::ReplicationClientConfig;
-using memgraph::replication::ReplicationState;
 using memgraph::replication::RoleMainData;
 using memgraph::replication::RoleReplicaData;
 
@@ -32,8 +38,8 @@ std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
     using enum RegisterReplicaError;
     case NAME_EXISTS:
       return "NAME_EXISTS";
-    case END_POINT_EXISTS:
-      return "END_POINT_EXISTS";
+    case ENDPOINT_EXISTS:
+      return "ENDPOINT_EXISTS";
     case CONNECTION_FAILED:
       return "CONNECTION_FAILED";
     case COULD_NOT_BE_PERSISTED:
@@ -45,34 +51,13 @@ std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
 ReplicationHandler::ReplicationHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
 
 bool ReplicationHandler::SetReplicationRoleMain() {
-  auto const main_handler = [](RoleMainData const &) {
+  auto const main_handler = [](RoleMainData &) {
     // If we are already MAIN, we don't want to change anything
     return false;
   };
+
   auto const replica_handler = [this](RoleReplicaData const &) {
-    // STEP 1) bring down all REPLICA servers
-    dbms_handler_.ForEach([](Database *db) {
-      auto *storage = db->storage();
-      // Remember old epoch + storage timestamp association
-      storage->PrepareForNewEpoch();
-    });
-
-    // STEP 2) Change to MAIN
-    // TODO: restore replication servers if false?
-    if (!dbms_handler_.ReplicationState().SetReplicationRoleMain()) {
-      // TODO: Handle recovery on failure???
-      return false;
-    }
-
-    // STEP 3) We are now MAIN, update storage local epoch
-    const auto &epoch =
-        std::get<RoleMainData>(std::as_const(dbms_handler_.ReplicationState()).ReplicationData()).epoch_;
-    dbms_handler_.ForEach([&](Database *db) {
-      auto *storage = db->storage();
-      storage->repl_storage_state_.epoch_ = epoch;
-    });
-
-    return true;
+    return memgraph::dbms::DoReplicaToMainPromotion(dbms_handler_);
   };
 
   // TODO: under lock
@@ -89,8 +74,8 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::
   // TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
   //      deleting the replica.
   // Remove database specific clients
-  dbms_handler_.ForEach([&](Database *db) {
-    auto *storage = db->storage();
+  dbms_handler_.ForEach([&](DatabaseAccess db_acc) {
+    auto *storage = db_acc->storage();
     storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
   });
   // Remove instance level clients
@@ -105,15 +90,7 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::
                                      // ASSERT
                                      return false;
                                    },
-                                   [this](RoleReplicaData const &data) {
-                                     // Register handlers
-                                     InMemoryReplicationHandlers::Register(&dbms_handler_, *data.server);
-                                     if (!data.server->Start()) {
-                                       spdlog::error("Unable to start the replication server.");
-                                       return false;
-                                     }
-                                     return true;
-                                   }},
+                                   [this](RoleReplicaData const &data) { return StartRpcServer(dbms_handler_, data); }},
                  dbms_handler_.ReplicationState().ReplicationData());
   // TODO Handle error (restore to main?)
   return success;
@@ -123,60 +100,48 @@ auto ReplicationHandler::RegisterReplica(const memgraph::replication::Replicatio
     -> memgraph::utils::BasicResult<RegisterReplicaError> {
   MG_ASSERT(dbms_handler_.ReplicationState().IsMain(), "Only main instance can register a replica!");
 
-  auto instance_client = dbms_handler_.ReplicationState().RegisterReplica(config);
-  if (instance_client.HasError()) switch (instance_client.GetError()) {
+  auto maybe_client = dbms_handler_.ReplicationState().RegisterReplica(config);
+  if (maybe_client.HasError()) {
+    switch (maybe_client.GetError()) {
       case memgraph::replication::RegisterReplicaError::NOT_MAIN:
         MG_ASSERT(false, "Only main instance can register a replica!");
         return {};
       case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
         return memgraph::dbms::RegisterReplicaError::NAME_EXISTS;
-      case memgraph::replication::RegisterReplicaError::END_POINT_EXISTS:
-        return memgraph::dbms::RegisterReplicaError::END_POINT_EXISTS;
+      case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS:
+        return memgraph::dbms::RegisterReplicaError::ENDPOINT_EXISTS;
       case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
         return memgraph::dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
       case memgraph::replication::RegisterReplicaError::SUCCESS:
         break;
     }
+  }
 
   if (!allow_mt_repl && dbms_handler_.All().size() > 1) {
     spdlog::warn("Multi-tenant replication is currently not supported!");
   }
 
-  bool all_clients_good = true;
+#ifdef MG_ENTERPRISE
+  // Update system before enabling individual storage <-> replica clients
+  dbms_handler_.SystemRestore(*maybe_client.GetValue());
+#endif
 
-  // Add database specific clients (NOTE Currently all databases are connected to each replica)
-  dbms_handler_.ForEach([&](Database *db) {
-    auto *storage = db->storage();
-    if (!allow_mt_repl && storage->id() != kDefaultDB) {
-      return;
-    }
-    // TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
-    if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return;
-
-    all_clients_good &=
-        storage->repl_storage_state_.replication_clients_.WithLock([storage, &instance_client](auto &storage_clients) {
-          auto client = std::make_unique<storage::ReplicationStorageClient>(*instance_client.GetValue());
-          client->Start(storage);
-          // After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
-          // MAYBE_BEHIND isn't a statement of the current state, this is the default value
-          // Failed to start due to branching of MAIN and REPLICA
-          if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
-            return false;
-          }
-          storage_clients.push_back(std::move(client));
-          return true;
-        });
-  });
+  const auto dbms_error = memgraph::dbms::HandleRegisterReplicaStatus(maybe_client);
+  if (dbms_error.has_value()) {
+    return *dbms_error;
+  }
+  auto &instance_client_ptr = maybe_client.GetValue();
+  const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler_, *instance_client_ptr);
 
   // NOTE Currently if any databases fails, we revert back
   if (!all_clients_good) {
-    spdlog::error("Failed to register all databases to the REPLICA \"{}\"", config.name);
+    spdlog::error("Failed to register all databases on the REPLICA \"{}\"", config.name);
     UnregisterReplica(config.name);
     return RegisterReplicaError::CONNECTION_FAILED;
   }
 
   // No client error, start instance level client
-  StartReplicaClient(dbms_handler_, *instance_client.GetValue());
+  StartReplicaClient(dbms_handler_, *instance_client_ptr);
   return {};
 }
 
@@ -189,8 +154,8 @@ auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterR
       return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
     }
     // Remove database specific clients
-    dbms_handler_.ForEach([name](Database *db) {
-      db->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) {
+    dbms_handler_.ForEach([name](DatabaseAccess db_acc) {
+      db_acc->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) {
         std::erase_if(clients, [name](const auto &client) { return client->Name() == name; });
       });
     });
@@ -204,7 +169,7 @@ auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterR
                     dbms_handler_.ReplicationState().ReplicationData());
 }
 
-auto ReplicationHandler::GetRole() const -> memgraph::replication::ReplicationRole {
+auto ReplicationHandler::GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole {
   return dbms_handler_.ReplicationState().GetRole();
 }
 
@@ -214,20 +179,20 @@ bool ReplicationHandler::IsReplica() const { return dbms_handler_.ReplicationSta
 
 // Per storage
 // NOTE Storage will connect to all replicas. Future work might change this
-void RestoreReplication(replication::ReplicationState &repl_state, storage::Storage &storage) {
+void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc) {
   spdlog::info("Restoring replication role.");
 
   /// MAIN
-  auto const recover_main = [&storage](RoleMainData &mainData) {
+  auto const recover_main = [db_acc = std::move(db_acc)](RoleMainData &mainData) mutable {  // NOLINT
     // Each individual client has already been restored and started. Here we just go through each database and start its
     // client
     for (auto &instance_client : mainData.registered_replicas_) {
-      spdlog::info("Replica {} restoration started for {}.", instance_client.name_, storage.id());
-
-      const auto &ret = storage.repl_storage_state_.replication_clients_.WithLock(
-          [&](auto &storage_clients) -> utils::BasicResult<RegisterReplicaError> {
+      spdlog::info("Replica {} restoration started for {}.", instance_client.name_, db_acc->name());
+      const auto &ret = db_acc->storage()->repl_storage_state_.replication_clients_.WithLock(
+          [&, db_acc](auto &storage_clients) mutable -> utils::BasicResult<RegisterReplicaError> {
             auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
-            client->Start(&storage);
+            auto *storage = db_acc->storage();
+            client->Start(storage, std::move(db_acc));
             // After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
             // MAYBE_BEHIND isn't a statement of the current state, this is the default value
             // Failed to start due to branching of MAIN and REPLICA
@@ -244,7 +209,7 @@ void RestoreReplication(replication::ReplicationState &repl_state, storage::Stor
         LOG_FATAL("Failure when restoring replica {}: {}.", instance_client.name_,
                   RegisterReplicaErrorToString(ret.GetError()));
       }
-      spdlog::info("Replica {} restored for {}.", instance_client.name_, storage.id());
+      spdlog::info("Replica {} restored for {}.", instance_client.name_, db_acc->name());
     }
     spdlog::info("Replication role restored to MAIN.");
   };
@@ -259,4 +224,177 @@ void RestoreReplication(replication::ReplicationState &repl_state, storage::Stor
       },
       repl_state.ReplicationData());
 }
+
+namespace system_replication {
+#ifdef MG_ENTERPRISE
+void SystemHeartbeatHandler(const uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder) {
+  replication::SystemHeartbeatReq req;
+  replication::SystemHeartbeatReq::Load(&req, req_reader);
+
+  replication::SystemHeartbeatRes res(ts);
+  memgraph::slk::Save(res, res_builder);
+}
+
+void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
+  memgraph::storage::replication::CreateDatabaseReq req;
+  memgraph::slk::Load(&req, req_reader);
+
+  using memgraph::storage::replication::CreateDatabaseRes;
+  CreateDatabaseRes res(CreateDatabaseRes::Result::FAILURE);
+
+  // Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
+  //       of the set of databases. Hence no history exists to maintain regarding epoch change.
+  //       If MAIN has changed we need to check this new group_timestamp is consistent with
+  //       what we have so far.
+
+  if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) {
+    spdlog::debug("CreateDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
+                  dbms_handler.LastCommitedTS());
+    memgraph::slk::Save(res, res_builder);
+    return;
+  }
+
+  try {
+    // Create new
+    auto new_db = dbms_handler.Update(req.config);
+    if (new_db.HasValue()) {
+      // Successfully create db
+      dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
+      res = CreateDatabaseRes(CreateDatabaseRes::Result::SUCCESS);
+      spdlog::debug("CreateDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
+    }
+  } catch (...) {
+    // Failure
+  }
+
+  memgraph::slk::Save(res, res_builder);
+}
+
+void DropDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
+  memgraph::storage::replication::DropDatabaseReq req;
+  memgraph::slk::Load(&req, req_reader);
+
+  using memgraph::storage::replication::DropDatabaseRes;
+  DropDatabaseRes res(DropDatabaseRes::Result::FAILURE);
+
+  // Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
+  //       of the set of databases. Hence no history exists to maintain regarding epoch change.
+  //       If MAIN has changed we need to check this new group_timestamp is consistent with
+  //       what we have so far.
+
+  if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) {
+    spdlog::debug("DropDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
+                  dbms_handler.LastCommitedTS());
+    memgraph::slk::Save(res, res_builder);
+    return;
+  }
+
+  try {
+    // NOTE: Single communication channel can exist at a time, no other database can be deleted/created at the moment.
+    auto new_db = dbms_handler.Delete(req.uuid);
+    if (new_db.HasError()) {
+      if (new_db.GetError() == DeleteError::NON_EXISTENT) {
+        // Nothing to drop
+        dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
+        res = DropDatabaseRes(DropDatabaseRes::Result::NO_NEED);
+      }
+    } else {
+      // Successfully drop db
+      dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
+      res = DropDatabaseRes(DropDatabaseRes::Result::SUCCESS);
+      spdlog::debug("DropDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
+    }
+  } catch (...) {
+    // Failure
+  }
+
+  memgraph::slk::Save(res, res_builder);
+}
+
+void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
+  // TODO Speed up
+  memgraph::storage::replication::SystemRecoveryReq req;
+  memgraph::slk::Load(&req, req_reader);
+
+  using memgraph::storage::replication::SystemRecoveryRes;
+  SystemRecoveryRes res(SystemRecoveryRes::Result::FAILURE);
+
+  utils::OnScopeExit send_on_exit([&]() { memgraph::slk::Save(res, res_builder); });
+
+  // Get all current dbs
+  auto old = dbms_handler.All();
+
+  // Check/create the incoming dbs
+  for (const auto &config : req.database_configs) {
+    // Missing db
+    try {
+      if (dbms_handler.Update(config).HasError()) {
+        spdlog::debug("SystemRecoveryHandler: Failed to update database \"{}\".", config.name);
+        return;  // Send failure on exit
+      }
+    } catch (const UnknownDatabaseException &) {
+      spdlog::debug("SystemRecoveryHandler: UnknownDatabaseException");
+      return;  // Send failure on exit
+    }
+    const auto it = std::find(old.begin(), old.end(), config.name);
+    if (it != old.end()) old.erase(it);
+  }
+
+  // Delete all the leftover old dbs
+  for (const auto &remove_db : old) {
+    const auto del = dbms_handler.Delete(remove_db);
+    if (del.HasError()) {
+      // Some errors are not terminal
+      if (del.GetError() == DeleteError::DEFAULT_DB || del.GetError() == DeleteError::NON_EXISTENT) {
+        spdlog::debug("SystemRecoveryHandler: Dropped database \"{}\".", remove_db);
+        continue;
+      }
+      spdlog::debug("SystemRecoveryHandler: Failed to drop database \"{}\".", remove_db);
+      return;  // Send failure on exit
+    }
+  }
+  // Successfully recovered
+  dbms_handler.SetLastCommitedTS(req.forced_group_timestamp);
+  spdlog::debug("SystemRecoveryHandler: SUCCESS updated LCTS to {}", req.forced_group_timestamp);
+  res = SystemRecoveryRes(SystemRecoveryRes::Result::SUCCESS);
+}
+#endif
+
+void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler) {
+#ifdef MG_ENTERPRISE
+  data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>(
+      [&dbms_handler](auto *req_reader, auto *res_builder) {
+        spdlog::debug("Received SystemHeartbeatRpc");
+        SystemHeartbeatHandler(dbms_handler.LastCommitedTS(), req_reader, res_builder);
+      });
+  data.server->rpc_server_.Register<storage::replication::CreateDatabaseRpc>(
+      [&dbms_handler](auto *req_reader, auto *res_builder) {
+        spdlog::debug("Received CreateDatabaseRpc");
+        CreateDatabaseHandler(dbms_handler, req_reader, res_builder);
+      });
+  data.server->rpc_server_.Register<storage::replication::DropDatabaseRpc>(
+      [&dbms_handler](auto *req_reader, auto *res_builder) {
+        spdlog::debug("Received DropDatabaseRpc");
+        DropDatabaseHandler(dbms_handler, req_reader, res_builder);
+      });
+  data.server->rpc_server_.Register<storage::replication::SystemRecoveryRpc>(
+      [&dbms_handler](auto *req_reader, auto *res_builder) {
+        spdlog::debug("Received SystemRecoveryRpc");
+        SystemRecoveryHandler(dbms_handler, req_reader, res_builder);
+      });
+#endif
+}
+}  // namespace system_replication
+
+bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data) {
+  // Register handlers
+  InMemoryReplicationHandlers::Register(&dbms_handler, *data.server);
+  system_replication::Register(data, dbms_handler);
+  // Start server
+  if (!data.server->Start()) {
+    spdlog::error("Unable to start the replication server.");
+    return false;
+  }
+  return true;
+}
 }  // namespace memgraph::dbms
diff --git a/src/dbms/replication_handler.hpp b/src/dbms/replication_handler.hpp
index dc95407b1..53c64e34b 100644
--- a/src/dbms/replication_handler.hpp
+++ b/src/dbms/replication_handler.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,11 +11,10 @@
 
 #pragma once
 
-#include "replication/role.hpp"
-#include "storage/v2/storage.hpp"
+#include "replication_coordination_glue/role.hpp"
+#include "dbms/database.hpp"
 #include "utils/result.hpp"
 
-// BEGIN fwd declares
 namespace memgraph::replication {
 struct ReplicationState;
 struct ReplicationServerConfig;
@@ -23,9 +22,11 @@ struct ReplicationClientConfig;
 }  // namespace memgraph::replication
 
 namespace memgraph::dbms {
+
 class DbmsHandler;
 
-enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
+enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
+
 enum class UnregisterReplicaResult : uint8_t {
   NOT_MAIN,
   COULD_NOT_BE_PERSISTED,
@@ -52,7 +53,7 @@ struct ReplicationHandler {
   auto UnregisterReplica(std::string_view name) -> UnregisterReplicaResult;
 
   // Helper pass-through (TODO: remove)
-  auto GetRole() const -> memgraph::replication::ReplicationRole;
+  auto GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole;
   bool IsMain() const;
   bool IsReplica() const;
 
@@ -62,6 +63,20 @@ struct ReplicationHandler {
 
 /// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
 /// TODO: extend to do multiple storages
-void RestoreReplication(replication::ReplicationState &repl_state, storage::Storage &storage);
+void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc);
+
+namespace system_replication {
+// System handlers
+#ifdef MG_ENTERPRISE
+void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
+void SystemHeartbeatHandler(uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder);
+void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
+#endif
+
+/// Register all DBMS level RPC handlers
+void Register(replication::RoleReplicaData const &data, DbmsHandler &dbms_handler);
+}  // namespace system_replication
+
+bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data);
 
 }  // namespace memgraph::dbms
diff --git a/src/dbms/transaction.hpp b/src/dbms/transaction.hpp
new file mode 100644
index 000000000..7167d9ec5
--- /dev/null
+++ b/src/dbms/transaction.hpp
@@ -0,0 +1,64 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <memory>
+#include "storage/v2/config.hpp"
+
+namespace memgraph::dbms {
+struct SystemTransaction {
+  struct Delta {
+    enum class Action {
+      CREATE_DATABASE,
+      DROP_DATABASE,
+    };
+
+    static constexpr struct CreateDatabase {
+    } create_database;
+    static constexpr struct DropDatabase {
+    } drop_database;
+
+    Delta(CreateDatabase /*tag*/, storage::SalientConfig config)
+        : action(Action::CREATE_DATABASE), config(std::move(config)) {}
+    Delta(DropDatabase /*tag*/, const utils::UUID &uuid) : action(Action::DROP_DATABASE), uuid(uuid) {}
+
+    Delta(const Delta &) = delete;
+    Delta(Delta &&) = delete;
+    Delta &operator=(const Delta &) = delete;
+    Delta &operator=(Delta &&) = delete;
+
+    ~Delta() {
+      switch (action) {
+        case Action::CREATE_DATABASE:
+          std::destroy_at(&config);
+          break;
+        case Action::DROP_DATABASE:
+          break;
+          // Some deltas might have special destructor handling
+      }
+    }
+
+    Action action;
+    union {
+      storage::SalientConfig config;
+      utils::UUID uuid;
+    };
+  };
+
+  explicit SystemTransaction(uint64_t timestamp) : system_timestamp(timestamp) {}
+
+  // Currently system transitions support a single delta
+  std::optional<Delta> delta{};
+  uint64_t system_timestamp;
+};
+
+}  // namespace memgraph::dbms
diff --git a/src/dbms/utils.hpp b/src/dbms/utils.hpp
new file mode 100644
index 000000000..fd5db9cf1
--- /dev/null
+++ b/src/dbms/utils.hpp
@@ -0,0 +1,133 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+#pragma once
+
+#include "dbms/dbms_handler.hpp"
+#include "dbms/replication_handler.hpp"
+#include "replication/include/replication/state.hpp"
+#include "utils/result.hpp"
+
+namespace memgraph::dbms {
+
+inline bool DoReplicaToMainPromotion(dbms::DbmsHandler &dbms_handler) {
+  auto &repl_state = dbms_handler.ReplicationState();
+  // STEP 1) bring down all REPLICA servers
+  dbms_handler.ForEach([](DatabaseAccess db_acc) {
+    auto *storage = db_acc->storage();
+    // Remember old epoch + storage timestamp association
+    storage->PrepareForNewEpoch();
+  });
+
+  // STEP 2) Change to MAIN
+  // TODO: restore replication servers if false?
+  if (!repl_state.SetReplicationRoleMain()) {
+    // TODO: Handle recovery on failure???
+    return false;
+  }
+
+  // STEP 3) We are now MAIN, update storage local epoch
+  const auto &epoch =
+      std::get<replication::RoleMainData>(std::as_const(dbms_handler.ReplicationState()).ReplicationData()).epoch_;
+  dbms_handler.ForEach([&](DatabaseAccess db_acc) {
+    auto *storage = db_acc->storage();
+    storage->repl_storage_state_.epoch_ = epoch;
+  });
+
+  return true;
+};
+
+inline bool SetReplicationRoleReplica(dbms::DbmsHandler &dbms_handler,
+                                      const memgraph::replication::ReplicationServerConfig &config) {
+  if (dbms_handler.ReplicationState().IsReplica()) {
+    return false;
+  }
+
+  // TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
+  //      deleting the replica.
+  // Remove database specific clients
+  dbms_handler.ForEach([&](DatabaseAccess db_acc) {
+    auto *storage = db_acc->storage();
+    storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
+  });
+  // Remove instance level clients
+  std::get<replication::RoleMainData>(dbms_handler.ReplicationState().ReplicationData()).registered_replicas_.clear();
+
+  // Creates the server
+  dbms_handler.ReplicationState().SetReplicationRoleReplica(config);
+
+  // Start
+  const auto success = std::visit(utils::Overloaded{[](replication::RoleMainData const &) {
+                                                      // ASSERT
+                                                      return false;
+                                                    },
+                                                    [&dbms_handler](replication::RoleReplicaData const &data) {
+                                                      return StartRpcServer(dbms_handler, data);
+                                                    }},
+                                  dbms_handler.ReplicationState().ReplicationData());
+  // TODO Handle error (restore to main?)
+  return success;
+}
+
+inline bool RegisterAllDatabasesClients(dbms::DbmsHandler &dbms_handler,
+                                        replication::ReplicationClient &instance_client) {
+  if (!allow_mt_repl && dbms_handler.All().size() > 1) {
+    spdlog::warn("Multi-tenant replication is currently not supported!");
+  }
+
+  bool all_clients_good = true;
+
+  // Add database specific clients (NOTE Currently all databases are connected to each replica)
+  dbms_handler.ForEach([&](DatabaseAccess db_acc) {
+    auto *storage = db_acc->storage();
+    if (!allow_mt_repl && storage->name() != kDefaultDB) {
+      return;
+    }
+    // TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
+    if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return;
+
+    all_clients_good &= storage->repl_storage_state_.replication_clients_.WithLock(
+        [storage, &instance_client, db_acc = std::move(db_acc)](auto &storage_clients) mutable {  // NOLINT
+          auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
+          // All good, start replica client
+          client->Start(storage, std::move(db_acc));
+          // After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
+          // MAYBE_BEHIND isn't a statement of the current state, this is the default value
+          // Failed to start due an error like branching of MAIN and REPLICA
+          if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
+            return false;  // TODO: sometimes we need to still add to storage_clients
+          }
+          storage_clients.push_back(std::move(client));
+          return true;
+        });
+  });
+
+  return all_clients_good;
+}
+
+inline std::optional<RegisterReplicaError> HandleRegisterReplicaStatus(
+    utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client) {
+  if (instance_client.HasError()) switch (instance_client.GetError()) {
+      case replication::RegisterReplicaError::NOT_MAIN:
+        MG_ASSERT(false, "Only main instance can register a replica!");
+        return {};
+      case replication::RegisterReplicaError::NAME_EXISTS:
+        return dbms::RegisterReplicaError::NAME_EXISTS;
+      case replication::RegisterReplicaError::ENDPOINT_EXISTS:
+        return dbms::RegisterReplicaError::ENDPOINT_EXISTS;
+      case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
+        return dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
+      case replication::RegisterReplicaError::SUCCESS:
+        break;
+    }
+  return {};
+}
+
+}  // namespace memgraph::dbms
diff --git a/src/flags/CMakeLists.txt b/src/flags/CMakeLists.txt
index e8988756f..e80438d1d 100644
--- a/src/flags/CMakeLists.txt
+++ b/src/flags/CMakeLists.txt
@@ -6,6 +6,7 @@ add_library(mg-flags STATIC audit.cpp
                             memory_limit.cpp
                             run_time_configurable.cpp
                             storage_mode.cpp
-                            query.cpp)
+                            query.cpp
+                            replication.cpp)
 target_include_directories(mg-flags PUBLIC ${CMAKE_SOURCE_DIR}/include)
 target_link_libraries(mg-flags PUBLIC spdlog::spdlog mg-settings mg-utils)
diff --git a/src/flags/all.hpp b/src/flags/all.hpp
index f7b44272a..f60f059d6 100644
--- a/src/flags/all.hpp
+++ b/src/flags/all.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,5 +17,6 @@
 #include "flags/log_level.hpp"
 #include "flags/memory_limit.hpp"
 #include "flags/query.hpp"
+#include "flags/replication.hpp"
 #include "flags/run_time_configurable.hpp"
 #include "flags/storage_mode.hpp"
diff --git a/src/flags/general.cpp b/src/flags/general.cpp
index 6bee2e5b3..cd2c95c60 100644
--- a/src/flags/general.cpp
+++ b/src/flags/general.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -66,6 +66,9 @@ DEFINE_bool(allow_load_csv, true, "Controls whether LOAD CSV clause is allowed i
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 30, "Storage garbage collector interval (in seconds).",
                         FLAG_IN_RANGE(1, 24UL * 3600));
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_VALIDATED_uint64(storage_python_gc_cycle_sec, 180,
+                        "Storage python full garbage collection interval (in seconds).", FLAG_IN_RANGE(1, 24UL * 3600));
 // NOTE: The `storage_properties_on_edges` flag must be the same here and in
 // `mg_import_csv`. If you change it, make sure to change it there as well.
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
@@ -128,12 +131,6 @@ DEFINE_uint64(storage_recovery_thread_count,
 DEFINE_bool(storage_enable_schema_metadata, false,
             "Controls whether metadata should be collected about the resident labels and edge types.");
 
-#ifdef MG_ENTERPRISE
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DEFINE_bool(storage_delete_on_drop, true,
-            "If set to true the query 'DROP DATABASE x' will delete the underlying storage as well.");
-#endif
-
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DEFINE_bool(telemetry_enabled, false,
             "Set to true to enable telemetry. We collect information about the "
@@ -159,13 +156,6 @@ DEFINE_string(pulsar_service_url, "", "Default URL used while connecting to Puls
 
 // Query flags.
 
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DEFINE_uint64(replication_replica_check_frequency_sec, 1,
-              "The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: "
-              "The MAIN instance allocates a new thread for each REPLICA.");
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica");
-
 DEFINE_VALIDATED_string(query_modules_directory, "",
                         "Directory where modules with custom query procedures are stored. "
                         "NOTE: Multiple comma-separated directories can be defined.",
@@ -205,3 +195,9 @@ DEFINE_HIDDEN_string(organization_name, "", "Organization name.");
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(),
               "Set to the regular expression that each user or role name must fulfill.");
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_string(auth_password_strength_regex, memgraph::glue::kDefaultPasswordRegex.data(),
+              "The regular expression that should be used to match the entire "
+              "entered password to ensure its strength.");
diff --git a/src/flags/general.hpp b/src/flags/general.hpp
index 890f32cd6..a1e8729ab 100644
--- a/src/flags/general.hpp
+++ b/src/flags/general.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -52,6 +52,8 @@ DECLARE_bool(allow_load_csv);
 // Storage flags.
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DECLARE_uint64(storage_gc_cycle_sec);
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_uint64(storage_python_gc_cycle_sec);
 // NOTE: The `storage_properties_on_edges` flag must be the same here and in
 // `mg_import_csv`. If you change it, make sure to change it there as well.
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
@@ -82,10 +84,6 @@ DECLARE_bool(storage_parallel_schema_recovery);
 DECLARE_uint64(storage_recovery_thread_count);
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DECLARE_bool(storage_enable_schema_metadata);
-#ifdef MG_ENTERPRISE
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DECLARE_bool(storage_delete_on_drop);
-#endif
 
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DECLARE_bool(telemetry_enabled);
@@ -114,14 +112,13 @@ namespace memgraph::flags {
 auto ParseQueryModulesDirectory() -> std::vector<std::filesystem::path>;
 }  // namespace memgraph::flags
 
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DECLARE_uint64(replication_replica_check_frequency_sec);
-// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
-DECLARE_bool(replication_restore_state_on_startup);
-
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DECLARE_string(license_key);
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DECLARE_string(organization_name);
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 DECLARE_string(auth_user_or_role_name_regex);
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_bool(auth_password_permit_null);
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_string(auth_password_strength_regex);
diff --git a/src/flags/replication.cpp b/src/flags/replication.cpp
new file mode 100644
index 000000000..3cd5187f3
--- /dev/null
+++ b/src/flags/replication.cpp
@@ -0,0 +1,26 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#include "replication.hpp"
+
+#ifdef MG_ENTERPRISE
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_bool(coordinator, false, "Controls whether the instance is a replication coordinator.");
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_uint32(coordinator_server_port, 0, "Port on which coordinator servers will be started.");
+#endif
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_uint64(replication_replica_check_frequency_sec, 1,
+              "The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: "
+              "The MAIN instance allocates a new thread for each REPLICA.");
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica");
diff --git a/src/flags/replication.hpp b/src/flags/replication.hpp
new file mode 100644
index 000000000..16f4c74d2
--- /dev/null
+++ b/src/flags/replication.hpp
@@ -0,0 +1,26 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include "gflags/gflags.h"
+
+#ifdef MG_ENTERPRISE
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_bool(coordinator);
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_uint32(coordinator_server_port);
+#endif
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_uint64(replication_replica_check_frequency_sec);
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+DECLARE_bool(replication_restore_state_on_startup);
diff --git a/src/flags/storage_mode.cpp b/src/flags/storage_mode.cpp
index b342719dd..63e9948fd 100644
--- a/src/flags/storage_mode.cpp
+++ b/src/flags/storage_mode.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,20 +17,14 @@
 
 #include "gflags/gflags.h"
 
-#include <array>
-
-inline constexpr std::array storage_mode_mappings{
-    std::pair{std::string_view{"IN_MEMORY_TRANSACTIONAL"}, memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL},
-    std::pair{std::string_view{"IN_MEMORY_ANALYTICAL"}, memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL},
-    std::pair{std::string_view{"ON_DISK_TRANSACTIONAL"}, memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL}};
-
 const std::string storage_mode_help_string =
     fmt::format("Default storage mode Memgraph uses. Allowed values: {}",
-                memgraph::utils::GetAllowedEnumValuesString(storage_mode_mappings));
+                memgraph::utils::GetAllowedEnumValuesString(memgraph::storage::storage_mode_mappings));
 
 // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
 DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_help_string.c_str(), {
-  if (const auto result = memgraph::utils::IsValidEnumValueString(value, storage_mode_mappings); result.HasError()) {
+  if (const auto result = memgraph::utils::IsValidEnumValueString(value, memgraph::storage::storage_mode_mappings);
+      result.HasError()) {
     switch (result.GetError()) {
       case memgraph::utils::ValidationError::EmptyValue: {
         std::cout << "Storage mode cannot be empty." << std::endl;
@@ -38,7 +32,7 @@ DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_he
       }
       case memgraph::utils::ValidationError::InvalidValue: {
         std::cout << "Invalid value for storage mode. Allowed values: "
-                  << memgraph::utils::GetAllowedEnumValuesString(storage_mode_mappings) << std::endl;
+                  << memgraph::utils::GetAllowedEnumValuesString(memgraph::storage::storage_mode_mappings) << std::endl;
         break;
       }
     }
@@ -48,8 +42,8 @@ DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_he
 });
 
 memgraph::storage::StorageMode memgraph::flags::ParseStorageMode() {
-  const auto storage_mode =
-      memgraph::utils::StringToEnum<memgraph::storage::StorageMode>(FLAGS_storage_mode, storage_mode_mappings);
+  const auto storage_mode = memgraph::utils::StringToEnum<memgraph::storage::StorageMode>(
+      FLAGS_storage_mode, memgraph::storage::storage_mode_mappings);
   MG_ASSERT(storage_mode, "Invalid storage mode");
   return *storage_mode;
 }
diff --git a/src/glue/SessionHL.cpp b/src/glue/SessionHL.cpp
index bff12d188..61c1ab26f 100644
--- a/src/glue/SessionHL.cpp
+++ b/src/glue/SessionHL.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -80,7 +80,7 @@ std::vector<memgraph::communication::bolt::Value> TypedValueResultStreamBase::De
   std::vector<memgraph::communication::bolt::Value> decoded_values;
   decoded_values.reserve(values.size());
   for (const auto &v : values) {
-    auto maybe_value = memgraph::glue::ToBoltValue(v, *storage_, memgraph::storage::View::NEW);
+    auto maybe_value = memgraph::glue::ToBoltValue(v, storage_, memgraph::storage::View::NEW);
     if (maybe_value.HasError()) {
       switch (maybe_value.GetError()) {
         case memgraph::storage::Error::DELETED_OBJECT:
@@ -112,14 +112,14 @@ std::string SessionHL::GetDefaultDB() {
   if (user_.has_value()) {
     return user_->db_access().GetDefault();
   }
-  return memgraph::dbms::kDefaultDB;
+  return std::string{memgraph::dbms::kDefaultDB};
 }
 #endif
 
 std::string SessionHL::GetCurrentDB() const {
   if (!interpreter_.current_db_.db_acc_) return "";
   const auto *db = interpreter_.current_db_.db_acc_->get();
-  return db->id();
+  return db->name();
 }
 
 std::optional<std::string> SessionHL::GetServerNameForInit() {
@@ -167,10 +167,10 @@ std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Discard(s
 std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Pull(SessionHL::TEncoder *encoder,
                                                                             std::optional<int> n,
                                                                             std::optional<int> qid) {
-  // TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
-  auto *db = interpreter_.current_db_.db_acc_->get();
   try {
-    TypedValueResultStream<TEncoder> stream(encoder, db->storage());
+    auto &db = interpreter_.current_db_.db_acc_;
+    auto *storage = db ? db->get()->storage() : nullptr;
+    TypedValueResultStream<TEncoder> stream(encoder, storage);
     return DecodeSummary(interpreter_.Pull(&stream, n, qid));
   } catch (const memgraph::query::QueryException &e) {
     // Count the number of specific exceptions thrown
@@ -193,17 +193,17 @@ std::pair<std::vector<std::string>, std::optional<int>> SessionHL::Interpret(
   for (const auto &[key, bolt_param] : params) {
     params_pv.emplace(key, ToPropertyValue(bolt_param));
   }
+
+#ifdef MG_ENTERPRISE
   const std::string *username{nullptr};
   if (user_) {
     username = &user_->username();
   }
 
-#ifdef MG_ENTERPRISE
-  // TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
-  auto *db = interpreter_.current_db_.db_acc_->get();
   if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
+    auto &db = interpreter_.current_db_.db_acc_;
     audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query,
-                       memgraph::storage::PropertyValue(params_pv), db->id());
+                       memgraph::storage::PropertyValue(params_pv), db ? db->get()->name() : "no known database");
   }
 #endif
   try {
@@ -351,11 +351,11 @@ SessionHL::~SessionHL() {
 
 std::map<std::string, memgraph::communication::bolt::Value> SessionHL::DecodeSummary(
     const std::map<std::string, memgraph::query::TypedValue> &summary) {
-  // TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
-  auto *db = interpreter_.current_db_.db_acc_->get();
+  auto &db_acc = interpreter_.current_db_.db_acc_;
+  auto *storage = db_acc ? db_acc->get()->storage() : nullptr;
   std::map<std::string, memgraph::communication::bolt::Value> decoded_summary;
   for (const auto &kv : summary) {
-    auto maybe_value = ToBoltValue(kv.second, *db->storage(), memgraph::storage::View::NEW);
+    auto maybe_value = ToBoltValue(kv.second, storage, memgraph::storage::View::NEW);
     if (maybe_value.HasError()) {
       switch (maybe_value.GetError()) {
         case memgraph::storage::Error::DELETED_OBJECT:
diff --git a/src/glue/auth.cpp b/src/glue/auth.cpp
index 8344ad49d..9be5cd87b 100644
--- a/src/glue/auth.cpp
+++ b/src/glue/auth.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -66,6 +66,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
       return auth::Permission::MULTI_DATABASE_EDIT;
     case query::AuthQuery::Privilege::MULTI_DATABASE_USE:
       return auth::Permission::MULTI_DATABASE_USE;
+    case query::AuthQuery::Privilege::COORDINATOR:
+      return auth::Permission::COORDINATOR;
   }
 }
 
diff --git a/src/glue/auth_global.hpp b/src/glue/auth_global.hpp
index 4675b6978..008960c76 100644
--- a/src/glue/auth_global.hpp
+++ b/src/glue/auth_global.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,4 +13,5 @@
 
 namespace memgraph::glue {
 inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+";
+static constexpr std::string_view kDefaultPasswordRegex = ".+";
 }  // namespace memgraph::glue
diff --git a/src/glue/auth_handler.cpp b/src/glue/auth_handler.cpp
index b4ebfcd2a..f3efb6ba0 100644
--- a/src/glue/auth_handler.cpp
+++ b/src/glue/auth_handler.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -249,25 +249,10 @@ std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivile
 namespace memgraph::glue {
 
 AuthQueryHandler::AuthQueryHandler(
-    memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
-    std::string name_regex_string)
-    : auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
+    memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
+    : auth_(auth) {}
 
 bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password) {
-  if (name_regex_string_ != kDefaultUserRoleRegex) {
-    if (const auto license_check_result =
-            memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings);
-        license_check_result.HasError()) {
-      throw memgraph::auth::AuthException(
-          "Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
-          "(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
-          kDefaultUserRoleRegex,
-          memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
-    }
-  }
-  if (!std::regex_match(username, name_regex_)) {
-    throw query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     const auto [first_user, user_added] = std::invoke([&, this] {
       auto locked_auth = auth_->Lock();
@@ -294,7 +279,7 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option
       );
 #ifdef MG_ENTERPRISE
       GrantDatabaseToUser(auth::kAllDatabases, username);
-      SetMainDatabase(username, dbms::kDefaultDB);
+      SetMainDatabase(dbms::kDefaultDB, username);
 #endif
     }
 
@@ -305,9 +290,6 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option
 }
 
 bool AuthQueryHandler::DropUser(const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
@@ -319,16 +301,13 @@ bool AuthQueryHandler::DropUser(const std::string &username) {
 }
 
 void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
     if (!user) {
       throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
     }
-    user->UpdatePassword(password);
+    locked_auth->UpdatePassword(*user, password);
     locked_auth->SaveUser(*user);
   } catch (const memgraph::auth::AuthException &e) {
     throw memgraph::query::QueryRuntimeException(e.what());
@@ -336,9 +315,6 @@ void AuthQueryHandler::SetPassword(const std::string &username, const std::optio
 }
 
 bool AuthQueryHandler::CreateRole(const std::string &rolename) {
-  if (!std::regex_match(rolename, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid role name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     return locked_auth->AddRole(rolename).has_value();
@@ -349,9 +325,6 @@ bool AuthQueryHandler::CreateRole(const std::string &rolename) {
 
 #ifdef MG_ENTERPRISE
 bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db, const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
@@ -363,9 +336,6 @@ bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db, const std::
 }
 
 bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db, const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
@@ -378,9 +348,6 @@ bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db, const std::str
 
 std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatabasePrivileges(
     const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
-  }
   try {
     auto locked_auth = auth_->ReadLock();
     auto user = locked_auth->GetUser(username);
@@ -393,10 +360,7 @@ std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatab
   }
 }
 
-bool AuthQueryHandler::SetMainDatabase(const std::string &db, const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
+bool AuthQueryHandler::SetMainDatabase(std::string_view db, const std::string &username) {
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
@@ -417,9 +381,6 @@ void AuthQueryHandler::DeleteDatabase(std::string_view db) {
 #endif
 
 bool AuthQueryHandler::DropRole(const std::string &rolename) {
-  if (!std::regex_match(rolename, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid role name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto role = locked_auth->GetRole(rolename);
@@ -465,9 +426,6 @@ std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetRolenames() {
 }
 
 std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     auto locked_auth = auth_->ReadLock();
     auto user = locked_auth->GetUser(username);
@@ -485,9 +443,6 @@ std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::strin
 }
 
 std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(const std::string &rolename) {
-  if (!std::regex_match(rolename, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid role name.");
-  }
   try {
     auto locked_auth = auth_->ReadLock();
     auto role = locked_auth->GetRole(rolename);
@@ -507,12 +462,6 @@ std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(c
 }
 
 void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
-  if (!std::regex_match(rolename, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid role name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
@@ -535,9 +484,6 @@ void AuthQueryHandler::SetRole(const std::string &username, const std::string &r
 }
 
 void AuthQueryHandler::ClearRole(const std::string &username) {
-  if (!std::regex_match(username, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user name.");
-  }
   try {
     auto locked_auth = auth_->Lock();
     auto user = locked_auth->GetUser(username);
@@ -552,9 +498,6 @@ void AuthQueryHandler::ClearRole(const std::string &username) {
 }
 
 std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivileges(const std::string &user_or_role) {
-  if (!std::regex_match(user_or_role, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
-  }
   try {
     auto locked_auth = auth_->ReadLock();
     std::vector<std::vector<memgraph::query::TypedValue>> grants;
@@ -704,9 +647,6 @@ void AuthQueryHandler::EditPermissions(
     const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
 #endif
 ) {
-  if (!std::regex_match(user_or_role, name_regex_)) {
-    throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
-  }
   try {
     std::vector<memgraph::auth::Permission> permissions;
     permissions.reserve(privileges.size());
diff --git a/src/glue/auth_handler.hpp b/src/glue/auth_handler.hpp
index 8798c150a..c226a4560 100644
--- a/src/glue/auth_handler.hpp
+++ b/src/glue/auth_handler.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -24,12 +24,9 @@ namespace memgraph::glue {
 
 class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
   memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
-  std::string name_regex_string_;
-  std::regex name_regex_;
 
  public:
-  AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
-                   std::string name_regex_string);
+  AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth);
 
   bool CreateUser(const std::string &username, const std::optional<std::string> &password) override;
 
@@ -44,7 +41,7 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
 
   std::vector<std::vector<memgraph::query::TypedValue>> GetDatabasePrivileges(const std::string &username) override;
 
-  bool SetMainDatabase(const std::string &db, const std::string &username) override;
+  bool SetMainDatabase(std::string_view db, const std::string &username) override;
 
   void DeleteDatabase(std::string_view db) override;
 #endif
diff --git a/src/glue/communication.cpp b/src/glue/communication.cpp
index 60181e877..2c71e37c7 100644
--- a/src/glue/communication.cpp
+++ b/src/glue/communication.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -73,8 +73,14 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const query::EdgeAccessor
   return ToBoltEdge(edge.impl_, db, view);
 }
 
-storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage::Storage &db, storage::View view) {
+storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage::Storage *db, storage::View view) {
+  auto check_db = [db]() {
+    if (db == nullptr) [[unlikely]]
+      throw communication::bolt::ValueException("Database needed for TypeValue conversion.");
+  };
+
   switch (value.type()) {
+    // No database needed
     case query::TypedValue::Type::Null:
       return Value();
     case query::TypedValue::Type::Bool:
@@ -85,16 +91,16 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
       return Value(value.ValueDouble());
     case query::TypedValue::Type::String:
       return Value(std::string(value.ValueString()));
-    case query::TypedValue::Type::List: {
-      std::vector<Value> values;
-      values.reserve(value.ValueList().size());
-      for (const auto &v : value.ValueList()) {
-        auto maybe_value = ToBoltValue(v, db, view);
-        if (maybe_value.HasError()) return maybe_value.GetError();
-        values.emplace_back(std::move(*maybe_value));
-      }
-      return Value(std::move(values));
-    }
+    case query::TypedValue::Type::Date:
+      return Value(value.ValueDate());
+    case query::TypedValue::Type::LocalTime:
+      return Value(value.ValueLocalTime());
+    case query::TypedValue::Type::LocalDateTime:
+      return Value(value.ValueLocalDateTime());
+    case query::TypedValue::Type::Duration:
+      return Value(value.ValueDuration());
+
+    // Database potentially not required
     case query::TypedValue::Type::Map: {
       std::map<std::string, Value> map;
       for (const auto &kv : value.ValueMap()) {
@@ -104,35 +110,48 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
       }
       return Value(std::move(map));
     }
+
+    // Database is required
+    case query::TypedValue::Type::List: {
+      check_db();
+      std::vector<Value> values;
+      values.reserve(value.ValueList().size());
+      for (const auto &v : value.ValueList()) {
+        auto maybe_value = ToBoltValue(v, db, view);
+        if (maybe_value.HasError()) return maybe_value.GetError();
+        values.emplace_back(std::move(*maybe_value));
+      }
+      return Value(std::move(values));
+    }
     case query::TypedValue::Type::Vertex: {
-      auto maybe_vertex = ToBoltVertex(value.ValueVertex(), db, view);
+      check_db();
+      auto maybe_vertex = ToBoltVertex(value.ValueVertex(), *db, view);
       if (maybe_vertex.HasError()) return maybe_vertex.GetError();
       return Value(std::move(*maybe_vertex));
     }
     case query::TypedValue::Type::Edge: {
-      auto maybe_edge = ToBoltEdge(value.ValueEdge(), db, view);
+      check_db();
+      auto maybe_edge = ToBoltEdge(value.ValueEdge(), *db, view);
       if (maybe_edge.HasError()) return maybe_edge.GetError();
       return Value(std::move(*maybe_edge));
     }
     case query::TypedValue::Type::Path: {
-      auto maybe_path = ToBoltPath(value.ValuePath(), db, view);
+      check_db();
+      auto maybe_path = ToBoltPath(value.ValuePath(), *db, view);
       if (maybe_path.HasError()) return maybe_path.GetError();
       return Value(std::move(*maybe_path));
     }
-    case query::TypedValue::Type::Date:
-      return Value(value.ValueDate());
-    case query::TypedValue::Type::LocalTime:
-      return Value(value.ValueLocalTime());
-    case query::TypedValue::Type::LocalDateTime:
-      return Value(value.ValueLocalDateTime());
-    case query::TypedValue::Type::Duration:
-      return Value(value.ValueDuration());
-    case query::TypedValue::Type::Function:
-      throw communication::bolt::ValueException("Unsupported conversion from TypedValue::Function to Value");
-    case query::TypedValue::Type::Graph:
-      auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view);
+    case query::TypedValue::Type::Graph: {
+      check_db();
+      auto maybe_graph = ToBoltGraph(value.ValueGraph(), *db, view);
       if (maybe_graph.HasError()) return maybe_graph.GetError();
       return Value(std::move(*maybe_graph));
+    }
+
+    // Unsupported conversions
+    case query::TypedValue::Type::Function: {
+      throw communication::bolt::ValueException("Unsupported conversion from TypedValue::Function to Value");
+    }
   }
 }
 
diff --git a/src/glue/communication.hpp b/src/glue/communication.hpp
index 0e3b39f4d..737f32db2 100644
--- a/src/glue/communication.hpp
+++ b/src/glue/communication.hpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2023 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -65,7 +65,7 @@ storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(c
 /// @param storage::View for ToBoltVertex and ToBoltEdge.
 ///
 /// @throw std::bad_alloc
-storage::Result<communication::bolt::Value> ToBoltValue(const query::TypedValue &value, const storage::Storage &db,
+storage::Result<communication::bolt::Value> ToBoltValue(const query::TypedValue &value, const storage::Storage *db,
                                                         storage::View view);
 
 query::TypedValue ToTypedValue(const communication::bolt::Value &value);
diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt
index 128e87114..428cad4b2 100644
--- a/src/io/CMakeLists.txt
+++ b/src/io/CMakeLists.txt
@@ -8,4 +8,5 @@ find_package(fmt REQUIRED)
 find_package(Threads REQUIRED)
 
 add_library(mg-io STATIC ${io_src_files})
+add_library(mg::io ALIAS mg-io)
 target_link_libraries(mg-io stdc++fs Threads::Threads fmt::fmt mg-utils)
diff --git a/src/io/network/endpoint.cpp b/src/io/network/endpoint.cpp
index e719180f5..e9032e42e 100644
--- a/src/io/network/endpoint.cpp
+++ b/src/io/network/endpoint.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -139,18 +139,13 @@ Endpoint::Endpoint(std::string ip_address, uint16_t port) : address(std::move(ip
 }
 
 // NOLINTNEXTLINE
-Endpoint::Endpoint(needs_resolving_t, std::string hostname, uint16_t port) : port(port) {
-  address = ResolveHostnameIntoIpAddress(hostname, port);
-  IpFamily ip_family = GetIpFamily(address);
-  if (ip_family == IpFamily::NONE) {
-    throw NetworkError("Not a valid IPv4 or IPv6 address: {}", address);
-  }
-  family = ip_family;
-}
+Endpoint::Endpoint(needs_resolving_t, std::string hostname, uint16_t port)
+    : address(std::move(hostname)), port(port), family{GetIpFamily(address)} {}
 
 std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint) {
   // no need to cover the IpFamily::NONE case, as you can't even construct an
   // Endpoint object if the IpFamily is NONE (i.e. the IP address is invalid)
+  // unless you use DNS hostname
   if (endpoint.family == Endpoint::IpFamily::IP6) {
     return os << "[" << endpoint.address << "]"
               << ":" << endpoint.port;
@@ -166,12 +161,12 @@ bool Endpoint::IsResolvableAddress(const std::string &address, uint16_t port) {
   };
   addrinfo *info = nullptr;
   auto status = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints, &info);
-  freeaddrinfo(info);
+  if (info) freeaddrinfo(info);
   return status == 0;
 }
 
 std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrAddress(
-    const std::string &address, const std::optional<uint16_t> default_port = {}) {
+    const std::string &address, const std::optional<uint16_t> default_port) {
   const std::string delimiter = ":";
   std::vector<std::string> parts = utils::Split(address, delimiter);
   if (parts.size() == 1) {
@@ -189,34 +184,4 @@ std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrAddress(
   return std::nullopt;
 }
 
-std::string Endpoint::ResolveHostnameIntoIpAddress(const std::string &address, uint16_t port) {
-  addrinfo hints{
-      .ai_flags = AI_PASSIVE,
-      .ai_family = AF_UNSPEC,     // IPv4 and IPv6
-      .ai_socktype = SOCK_STREAM  // TCP socket
-  };
-  addrinfo *info = nullptr;
-  auto status = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints, &info);
-  if (status != 0) throw NetworkError(gai_strerror(status));
-
-  for (auto *result = info; result != nullptr; result = result->ai_next) {
-    if (result->ai_family == AF_INET) {
-      char ipstr[INET_ADDRSTRLEN];
-      auto *ipv4 = reinterpret_cast<struct sockaddr_in *>(result->ai_addr);
-      inet_ntop(AF_INET, &(ipv4->sin_addr), ipstr, sizeof(ipstr));
-      freeaddrinfo(info);
-      return ipstr;
-    }
-    if (result->ai_family == AF_INET6) {
-      char ipstr[INET6_ADDRSTRLEN];
-      auto *ipv6 = reinterpret_cast<struct sockaddr_in6 *>(result->ai_addr);
-      inet_ntop(AF_INET6, &(ipv6->sin6_addr), ipstr, sizeof(ipstr));
-      freeaddrinfo(info);
-      return ipstr;
-    }
-  }
-  freeaddrinfo(info);
-  throw NetworkError("Not a valid address: {}", address);
-}
-
 }  // namespace memgraph::io::network
diff --git a/src/io/network/socket.cpp b/src/io/network/socket.cpp
index 7394ea39d..2d949cd4d 100644
--- a/src/io/network/socket.cpp
+++ b/src/io/network/socket.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -15,6 +15,7 @@
 #include <poll.h>
 
 #include "io/network/addrinfo.hpp"
+#include "io/network/network_error.hpp"
 #include "io/network/socket.hpp"
 #include "utils/likely.hpp"
 #include "utils/logging.hpp"
@@ -55,17 +56,21 @@ bool Socket::IsOpen() const { return socket_ != -1; }
 bool Socket::Connect(const Endpoint &endpoint) {
   if (socket_ != -1) return false;
 
-  for (const auto &it : AddrInfo{endpoint}) {
-    int sfd = socket(it.ai_family, it.ai_socktype, it.ai_protocol);
-    if (sfd == -1) continue;
-    if (connect(sfd, it.ai_addr, it.ai_addrlen) == 0) {
-      socket_ = sfd;
-      endpoint_ = endpoint;
-      break;
+  try {
+    for (const auto &it : AddrInfo{endpoint}) {
+      int sfd = socket(it.ai_family, it.ai_socktype, it.ai_protocol);
+      if (sfd == -1) continue;
+      if (connect(sfd, it.ai_addr, it.ai_addrlen) == 0) {
+        socket_ = sfd;
+        endpoint_ = endpoint;
+        break;
+      }
+      // If the connect failed close the file descriptor to prevent file
+      // descriptors being leaked
+      close(sfd);
     }
-    // If the connect failed close the file descriptor to prevent file
-    // descriptors being leaked
-    close(sfd);
+  } catch (const NetworkError &e) {
+    return false;
   }
 
   return !(socket_ == -1);
diff --git a/src/kvstore/kvstore.cpp b/src/kvstore/kvstore.cpp
index 877d6f9bd..1219b8527 100644
--- a/src/kvstore/kvstore.cpp
+++ b/src/kvstore/kvstore.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -51,7 +51,7 @@ KVStore &KVStore::operator=(KVStore &&other) {
   return *this;
 }
 
-bool KVStore::Put(const std::string &key, const std::string &value) {
+bool KVStore::Put(std::string_view key, std::string_view value) {
   auto s = pimpl_->db->Put(rocksdb::WriteOptions(), key, value);
   return s.ok();
 }
@@ -65,7 +65,7 @@ bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) {
   return s.ok();
 }
 
-std::optional<std::string> KVStore::Get(const std::string &key) const noexcept {
+std::optional<std::string> KVStore::Get(std::string_view key) const noexcept {
   std::string value;
   auto s = pimpl_->db->Get(rocksdb::ReadOptions(), key, &value);
   if (!s.ok()) return std::nullopt;
diff --git a/src/kvstore/kvstore.hpp b/src/kvstore/kvstore.hpp
index a67d01c8c..b9675d75b 100644
--- a/src/kvstore/kvstore.hpp
+++ b/src/kvstore/kvstore.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -61,7 +61,7 @@ class KVStore final {
    * @return true if the value has been successfully stored.
    *         In case of any error false is going to be returned.
    */
-  bool Put(const std::string &key, const std::string &value);
+  bool Put(std::string_view key, std::string_view value);
 
   /**
    * Store values under the given keys.
@@ -81,7 +81,7 @@ class KVStore final {
    * @return Value for the given key. std::nullopt in case of any error
    *         OR the value doesn't exist.
    */
-  std::optional<std::string> Get(const std::string &key) const noexcept;
+  std::optional<std::string> Get(std::string_view key) const noexcept;
 
   /**
    * Deletes the key and corresponding value from storage.
diff --git a/src/license/license.cpp b/src/license/license.cpp
index ff5306dab..653e66222 100644
--- a/src/license/license.cpp
+++ b/src/license/license.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -92,7 +92,7 @@ void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &s
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 LicenseChecker global_license_checker;
 
-LicenseChecker::~LicenseChecker() { scheduler_.Stop(); }
+LicenseChecker::~LicenseChecker() { Finalize(); }
 
 std::pair<std::string, std::string> LicenseChecker::ExtractLicenseInfo(const utils::Settings &settings) const {
   if (license_info_override_) {
diff --git a/src/license/license.hpp b/src/license/license.hpp
index da47ae0ff..b2e7dcce4 100644
--- a/src/license/license.hpp
+++ b/src/license/license.hpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -89,6 +89,8 @@ struct LicenseChecker {
 
   utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &GetLicenseInfo();
 
+  void Finalize() { scheduler_.Stop(); }
+
  private:
   std::pair<std::string, std::string> ExtractLicenseInfo(const utils::Settings &settings) const;
   void RevalidateLicense(const utils::Settings &settings);
diff --git a/src/memgraph.cpp b/src/memgraph.cpp
index 057b30982..cbd63490e 100644
--- a/src/memgraph.cpp
+++ b/src/memgraph.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,13 +11,11 @@
 
 #include <cstdint>
 #include "audit/log.hpp"
-#include "communication/metrics.hpp"
 #include "communication/websocket/auth.hpp"
 #include "communication/websocket/server.hpp"
 #include "dbms/constants.hpp"
 #include "dbms/inmemory/replication_handlers.hpp"
 #include "flags/all.hpp"
-#include "flags/run_time_configurable.hpp"
 #include "glue/MonitoringServerT.hpp"
 #include "glue/ServerT.hpp"
 #include "glue/auth_checker.hpp"
@@ -33,9 +31,9 @@
 #include "query/procedure/module.hpp"
 #include "query/procedure/py_module.hpp"
 #include "requests/requests.hpp"
+#include "storage/v2/durability/durability.hpp"
 #include "telemetry/telemetry.hpp"
 #include "utils/signals.hpp"
-#include "utils/skip_list.hpp"
 #include "utils/sysinfo/memory.hpp"
 #include "utils/system_info.hpp"
 #include "utils/terminate_handler.hpp"
@@ -73,7 +71,7 @@ void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, memgraph::dbm
         spdlog::warn("{} The rest of the init-file will be run.", e.what());
       }
       if (audit_log) {
-        audit_log->Record("", "", line, {}, memgraph::dbms::kDefaultDB);
+        audit_log->Record("", "", line, {}, std::string{memgraph::dbms::kDefaultDB});
       }
     }
   }
@@ -163,7 +161,7 @@ int main(int argc, char **argv) {
         // libstd.
         auto gil = memgraph::py::EnsureGIL();
         // NOLINTNEXTLINE(hicpp-signed-bitwise)
-        auto *flag = PyLong_FromLong(RTLD_NOW | RTLD_DEEPBIND);
+        auto *flag = PyLong_FromLong(RTLD_NOW);
         auto *setdl = PySys_GetObject("setdlopenflags");
         MG_ASSERT(setdl);
         auto *arg = PyTuple_New(1);
@@ -184,6 +182,10 @@ int main(int argc, char **argv) {
                                                    "https://memgr.ph/python"));
   }
 
+  memgraph::utils::Scheduler python_gc_scheduler;
+  python_gc_scheduler.Run("Python GC", std::chrono::seconds(FLAGS_storage_python_gc_cycle_sec),
+                          [] { memgraph::query::procedure::PyCollectGarbage(); });
+
   // Initialize the communication library.
   memgraph::communication::SSLInit sslInit;
 
@@ -253,6 +255,8 @@ int main(int argc, char **argv) {
   // register all runtime settings
   memgraph::license::RegisterLicenseSettings(memgraph::license::global_license_checker,
                                              memgraph::utils::global_settings);
+  memgraph::utils::OnScopeExit global_license_finalizer([] { memgraph::license::global_license_checker.Finalize(); });
+
   memgraph::flags::run_time::Initialize();
 
   memgraph::license::global_license_checker.CheckEnvLicense();
@@ -294,8 +298,7 @@ int main(int argc, char **argv) {
   memgraph::storage::Config db_config{
       .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC,
              .interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)},
-      .items = {.properties_on_edges = FLAGS_storage_properties_on_edges,
-                .enable_schema_metadata = FLAGS_storage_enable_schema_metadata},
+
       .durability = {.storage_directory = FLAGS_data_directory,
                      .recover_on_startup = FLAGS_storage_recover_on_startup || FLAGS_data_recovery_on_startup,
                      .snapshot_retention_count = FLAGS_storage_snapshot_retention_count,
@@ -317,7 +320,14 @@ int main(int argc, char **argv) {
                .id_name_mapper_directory = FLAGS_data_directory + "/rocksdb_id_name_mapper",
                .durability_directory = FLAGS_data_directory + "/rocksdb_durability",
                .wal_directory = FLAGS_data_directory + "/rocksdb_wal"},
-      .storage_mode = memgraph::flags::ParseStorageMode()};
+      .salient.items = {.properties_on_edges = FLAGS_storage_properties_on_edges,
+                        .enable_schema_metadata = FLAGS_storage_enable_schema_metadata},
+      .salient.storage_mode = memgraph::flags::ParseStorageMode()};
+
+  memgraph::utils::Scheduler jemalloc_purge_scheduler;
+  jemalloc_purge_scheduler.Run("Jemalloc purge", std::chrono::seconds(FLAGS_storage_gc_cycle_sec),
+                               [] { memgraph::memory::PurgeUnusedMemory(); });
+
   if (FLAGS_storage_snapshot_interval_sec == 0) {
     if (FLAGS_storage_wal_enabled) {
       LOG_FATAL(
@@ -347,11 +357,10 @@ int main(int argc, char **argv) {
       .stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)};
 
   auto auth_glue =
-      [flag = FLAGS_auth_user_or_role_name_regex](
-          memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
-          std::unique_ptr<memgraph::query::AuthQueryHandler> &ah, std::unique_ptr<memgraph::query::AuthChecker> &ac) {
+      [](memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
+         std::unique_ptr<memgraph::query::AuthQueryHandler> &ah, std::unique_ptr<memgraph::query::AuthChecker> &ac) {
         // Glue high level auth implementations to the query side
-        ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth, flag);
+        ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth);
         ac = std::make_unique<memgraph::glue::AuthChecker>(auth);
         // Handle users passed via arguments
         auto *maybe_username = std::getenv(kMgUser);
@@ -367,9 +376,10 @@ int main(int argc, char **argv) {
         }
       };
 
-  // WIP
-  memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{data_directory /
-                                                                                                     "auth"};
+  memgraph::auth::Auth::Config auth_config{FLAGS_auth_user_or_role_name_regex, FLAGS_auth_password_strength_regex,
+                                           FLAGS_auth_password_permit_null};
+  memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{
+      data_directory / "auth", auth_config};
   std::unique_ptr<memgraph::query::AuthQueryHandler> auth_handler;
   std::unique_ptr<memgraph::query::AuthChecker> auth_checker;
   auth_glue(&auth_, auth_handler, auth_checker);
@@ -377,7 +387,7 @@ int main(int argc, char **argv) {
   memgraph::dbms::DbmsHandler dbms_handler(db_config
 #ifdef MG_ENTERPRISE
                                            ,
-                                           &auth_, FLAGS_data_recovery_on_startup, FLAGS_storage_delete_on_drop
+                                           &auth_, FLAGS_data_recovery_on_startup
 #endif
   );
   auto db_acc = dbms_handler.Get();
@@ -537,6 +547,7 @@ int main(int argc, char **argv) {
 
   memgraph::query::procedure::gModuleRegistry.UnloadAllModules();
 
+  python_gc_scheduler.Stop();
   Py_END_ALLOW_THREADS;
   // Shutdown Python
   Py_Finalize();
diff --git a/src/memory/global_memory_control.cpp b/src/memory/global_memory_control.cpp
index 57d97bcaa..ab75435ca 100644
--- a/src/memory/global_memory_control.cpp
+++ b/src/memory/global_memory_control.cpp
@@ -119,7 +119,7 @@ static bool my_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, siz
 
   memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(length));
   if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
-    GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
+    GetQueriesMemoryControl().TrackAllocOnCurrentThread(size);
   }
 
   return false;
diff --git a/src/memory/query_memory_control.cpp b/src/memory/query_memory_control.cpp
index 91730c900..5e569bd13 100644
--- a/src/memory/query_memory_control.cpp
+++ b/src/memory/query_memory_control.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -36,14 +36,22 @@ namespace memgraph::memory {
 
 void QueriesMemoryControl::UpdateThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) {
   auto accessor = thread_id_to_transaction_id.access();
-  accessor.insert({thread_id, transaction_id});
+  auto elem = accessor.find(thread_id);
+  if (elem == accessor.end()) {
+    accessor.insert({thread_id, {transaction_id, 1}});
+  } else {
+    elem->transaction_id.cnt++;
+  }
 }
 
 void QueriesMemoryControl::EraseThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) {
   auto accessor = thread_id_to_transaction_id.access();
   auto elem = accessor.find(thread_id);
   MG_ASSERT(elem != accessor.end() && elem->transaction_id == transaction_id);
-  accessor.remove(thread_id);
+  elem->transaction_id.cnt--;
+  if (elem->transaction_id.cnt == 0) {
+    accessor.remove(thread_id);
+  }
 }
 
 void QueriesMemoryControl::TrackAllocOnCurrentThread(size_t size) {
diff --git a/src/memory/query_memory_control.hpp b/src/memory/query_memory_control.hpp
index 901917757..3852027a5 100644
--- a/src/memory/query_memory_control.hpp
+++ b/src/memory/query_memory_control.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -78,9 +78,20 @@ class QueriesMemoryControl {
   bool IsThreadTracked();
 
  private:
+  struct TransactionId {
+    uint64_t id;
+    uint64_t cnt;
+
+    bool operator<(const TransactionId &other) const { return id < other.id; }
+    bool operator==(const TransactionId &other) const { return id == other.id; }
+
+    bool operator<(uint64_t other) const { return id < other; }
+    bool operator==(uint64_t other) const { return id == other; }
+  };
+
   struct ThreadIdToTransactionId {
     std::thread::id thread_id;
-    uint64_t transaction_id;
+    TransactionId transaction_id;
 
     bool operator<(const ThreadIdToTransactionId &other) const { return thread_id < other.thread_id; }
     bool operator==(const ThreadIdToTransactionId &other) const { return thread_id == other.thread_id; }
@@ -98,6 +109,9 @@ class QueriesMemoryControl {
 
     bool operator<(uint64_t other) const { return transaction_id < other; }
     bool operator==(uint64_t other) const { return transaction_id == other; }
+
+    bool operator<(TransactionId other) const { return transaction_id < other.id; }
+    bool operator==(TransactionId other) const { return transaction_id == other.id; }
   };
 
   utils::SkipList<ThreadIdToTransactionId> thread_id_to_transaction_id;
diff --git a/src/mg_import_csv.cpp b/src/mg_import_csv.cpp
index e0a82584b..cbfb905aa 100644
--- a/src/mg_import_csv.cpp
+++ b/src/mg_import_csv.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -32,6 +32,8 @@
 #include "utils/timer.hpp"
 #include "version.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 bool ValidateControlCharacter(const char *flagname, const std::string &value) {
   if (value.empty()) {
     printf("The argument '%s' cannot be empty\n", flagname);
@@ -425,7 +427,7 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st
                     const std::vector<Field> &fields, const std::vector<std::string> &additional_labels,
                     std::unordered_map<NodeId, memgraph::storage::Gid> *node_id_map) {
   std::optional<NodeId> id;
-  auto acc = store->Access();
+  auto acc = store->Access(ReplicationRole::MAIN);
   auto node = acc->CreateVertex();
   for (size_t i = 0; i < row.size(); ++i) {
     const auto &field = fields[i];
@@ -571,7 +573,7 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto
   if (!end_id) throw LoadException("END_ID must be set");
   if (!relationship_type) throw LoadException("Relationship TYPE must be set");
 
-  auto acc = store->Access();
+  auto acc = store->Access(ReplicationRole::MAIN);
   auto from_node = acc->FindVertex(*start_id, memgraph::storage::View::NEW);
   if (!from_node) throw LoadException("From node must be in the storage");
   auto to_node = acc->FindVertex(*end_id, memgraph::storage::View::NEW);
@@ -705,12 +707,11 @@ int main(int argc, char *argv[]) {
 
   std::unordered_map<NodeId, memgraph::storage::Gid> node_id_map;
   memgraph::storage::Config config{
-
-      .items = {.properties_on_edges = FLAGS_storage_properties_on_edges},
       .durability = {.storage_directory = FLAGS_data_directory,
                      .recover_on_startup = false,
                      .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED,
                      .snapshot_on_exit = true},
+      .salient = {.items = {.properties_on_edges = FLAGS_storage_properties_on_edges}},
   };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   auto store = memgraph::dbms::CreateInMemoryStorage(config, repl_state);
diff --git a/src/query/auth_query_handler.hpp b/src/query/auth_query_handler.hpp
index 908dd3ebc..693103354 100644
--- a/src/query/auth_query_handler.hpp
+++ b/src/query/auth_query_handler.hpp
@@ -57,7 +57,7 @@ class AuthQueryHandler {
 
   /// Return true if main database set successfully
   /// @throw QueryRuntimeException if an error ocurred.
-  virtual bool SetMainDatabase(const std::string &db, const std::string &username) = 0;
+  virtual bool SetMainDatabase(std::string_view db, const std::string &username) = 0;
 
   /// Delete database from all users
   /// @throw QueryRuntimeException if an error ocurred.
diff --git a/src/query/common.hpp b/src/query/common.hpp
index 6f45760fe..054714164 100644
--- a/src/query/common.hpp
+++ b/src/query/common.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -72,8 +72,9 @@ class TypedValueVectorCompare final {
 
 /// Raise QueryRuntimeException if the value for symbol isn't of expected type.
 inline void ExpectType(const Symbol &symbol, const TypedValue &value, TypedValue::Type expected) {
-  if (value.type() != expected)
+  if (value.type() != expected) [[unlikely]] {
     throw QueryRuntimeException("Expected a {} for '{}', but got {}.", expected, symbol.name(), value.type());
+  }
 }
 
 inline void ProcessError(const storage::Error error) {
diff --git a/src/query/config.hpp b/src/query/config.hpp
index 64e2da5bb..88c3dd00e 100644
--- a/src/query/config.hpp
+++ b/src/query/config.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
diff --git a/src/query/context.hpp b/src/query/context.hpp
index 3040d6e10..f1522053c 100644
--- a/src/query/context.hpp
+++ b/src/query/context.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
diff --git a/src/query/cypher_query_interpreter.cpp b/src/query/cypher_query_interpreter.cpp
index d3ddc22c4..30966119b 100644
--- a/src/query/cypher_query_interpreter.cpp
+++ b/src/query/cypher_query_interpreter.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,7 +12,6 @@
 #include "query/cypher_query_interpreter.hpp"
 #include "query/frontend/ast/cypher_main_visitor.hpp"
 #include "query/frontend/opencypher/parser.hpp"
-#include "utils/synchronized.hpp"
 
 // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
 DEFINE_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
@@ -80,7 +79,7 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
     // Convert the ANTLR4 parse tree into an AST.
     AstStorage ast_storage;
     frontend::ParsingContext context{.is_query_cached = true};
-    frontend::CypherMainVisitor visitor(context, &ast_storage);
+    frontend::CypherMainVisitor visitor(context, &ast_storage, &parameters);
 
     visitor.visit(parser->tree());
 
diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp
index ed7dde409..71b997d9e 100644
--- a/src/query/db_accessor.hpp
+++ b/src/query/db_accessor.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -541,7 +541,10 @@ class DbAccessor final {
 
   void AdvanceCommand() { accessor_->AdvanceCommand(); }
 
-  utils::BasicResult<storage::StorageManipulationError, void> Commit() { return accessor_->Commit(); }
+  utils::BasicResult<storage::StorageManipulationError, void> Commit(storage::CommitReplArgs reparg = {},
+                                                                     storage::DatabaseAccessProtector db_acc = {}) {
+    return accessor_->Commit(std::move(reparg), std::move(db_acc));
+  }
 
   void Abort() { accessor_->Abort(); }
 
diff --git a/src/query/dump.cpp b/src/query/dump.cpp
index a1421cbf9..2925023fb 100644
--- a/src/query/dump.cpp
+++ b/src/query/dump.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,6 +11,7 @@
 
 #include "query/dump.hpp"
 
+#include <algorithm>
 #include <iomanip>
 #include <limits>
 #include <map>
@@ -21,9 +22,11 @@
 
 #include <fmt/format.h>
 
+#include "dbms/database.hpp"
 #include "query/db_accessor.hpp"
 #include "query/exceptions.hpp"
 #include "query/stream.hpp"
+#include "query/trigger_context.hpp"
 #include "query/typed_value.hpp"
 #include "storage/v2/property_value.hpp"
 #include "storage/v2/storage.hpp"
@@ -260,10 +263,20 @@ void DumpUniqueConstraint(std::ostream *os, query::DbAccessor *dba, storage::Lab
   *os << " IS UNIQUE;";
 }
 
+const char *triggerPhaseToString(TriggerPhase phase) {
+  switch (phase) {
+    case TriggerPhase::BEFORE_COMMIT:
+      return "BEFORE COMMIT EXECUTE";
+    case TriggerPhase::AFTER_COMMIT:
+      return "AFTER COMMIT EXECUTE";
+  }
+}
+
 }  // namespace
 
-PullPlanDump::PullPlanDump(DbAccessor *dba)
+PullPlanDump::PullPlanDump(DbAccessor *dba, dbms::DatabaseAccess db_acc)
     : dba_(dba),
+      db_acc_(db_acc),
       vertices_iterable_(dba->Vertices(storage::View::OLD)),
       pull_chunks_{// Dump all label indices
                    CreateLabelIndicesPullChunk(),
@@ -282,7 +295,9 @@ PullPlanDump::PullPlanDump(DbAccessor *dba)
                    // Drop the internal index
                    CreateDropInternalIndexPullChunk(),
                    // Internal index cleanup
-                   CreateInternalIndexCleanupPullChunk()} {}
+                   CreateInternalIndexCleanupPullChunk(),
+                   // Dump all triggers
+                   CreateTriggersPullChunk()} {}
 
 bool PullPlanDump::Pull(AnyStream *stream, std::optional<int> n) {
   // Iterate all functions that stream some results.
@@ -536,6 +551,23 @@ PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexCleanupPullChunk() {
   };
 }
 
-void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream) { PullPlanDump(dba).Pull(stream, {}); }
+PullPlanDump::PullChunk PullPlanDump::CreateTriggersPullChunk() {
+  return [this](AnyStream *stream, std::optional<int>) {
+    auto triggers = db_acc_->trigger_store()->GetTriggerInfo();
+    for (const auto &trigger : triggers) {
+      std::ostringstream os;
+      auto trigger_statement_copy = trigger.statement;
+      std::replace(trigger_statement_copy.begin(), trigger_statement_copy.end(), '\n', ' ');
+      os << "CREATE TRIGGER " << trigger.name << " ON " << memgraph::query::TriggerEventTypeToString(trigger.event_type)
+         << " " << triggerPhaseToString(trigger.phase) << " " << trigger_statement_copy << ";";
+      stream->Result({TypedValue(os.str())});
+    }
+    return 0;
+  };
+}
+
+void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream, dbms::DatabaseAccess db_acc) {
+  PullPlanDump(dba, db_acc).Pull(stream, {});
+}
 
 }  // namespace memgraph::query
diff --git a/src/query/dump.hpp b/src/query/dump.hpp
index b15a8d9e9..a9d68d45c 100644
--- a/src/query/dump.hpp
+++ b/src/query/dump.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,18 +11,17 @@
 
 #pragma once
 
-#include <ostream>
-
+#include "dbms/database.hpp"
 #include "query/db_accessor.hpp"
 #include "query/stream.hpp"
 #include "storage/v2/storage.hpp"
 
 namespace memgraph::query {
 
-void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream);
+void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream, dbms::DatabaseAccess db_acc);
 
 struct PullPlanDump {
-  explicit PullPlanDump(query::DbAccessor *dba);
+  explicit PullPlanDump(query::DbAccessor *dba, dbms::DatabaseAccess db_acc);
 
   /// Pull the dump results lazily
   /// @return true if all results were returned, false otherwise
@@ -30,6 +29,7 @@ struct PullPlanDump {
 
  private:
   query::DbAccessor *dba_ = nullptr;
+  dbms::DatabaseAccess db_acc_;
 
   std::optional<storage::IndicesInfo> indices_info_ = std::nullopt;
   std::optional<storage::ConstraintsInfo> constraints_info_ = std::nullopt;
@@ -62,5 +62,6 @@ struct PullPlanDump {
   PullChunk CreateEdgePullChunk();
   PullChunk CreateDropInternalIndexPullChunk();
   PullChunk CreateInternalIndexCleanupPullChunk();
+  PullChunk CreateTriggersPullChunk();
 };
 }  // namespace memgraph::query
diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp
index ac8cc8fe8..147dc8710 100644
--- a/src/query/exceptions.hpp
+++ b/src/query/exceptions.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -195,6 +195,12 @@ class DatabaseContextRequiredException : public QueryRuntimeException {
   SPECIALIZE_GET_EXCEPTION_NAME(DatabaseContextRequiredException)
 };
 
+class ConcurrentSystemQueriesException : public QueryRuntimeException {
+ public:
+  using QueryRuntimeException::QueryRuntimeException;
+  SPECIALIZE_GET_EXCEPTION_NAME(ConcurrentSystemQueriesException)
+};
+
 class WriteVertexOperationInEdgeImportModeException : public QueryException {
  public:
   WriteVertexOperationInEdgeImportModeException()
@@ -253,6 +259,13 @@ class ReplicationModificationInMulticommandTxException : public QueryException {
   SPECIALIZE_GET_EXCEPTION_NAME(ReplicationModificationInMulticommandTxException)
 };
 
+class CoordinatorModificationInMulticommandTxException : public QueryException {
+ public:
+  CoordinatorModificationInMulticommandTxException()
+      : QueryException("Coordinator clause not allowed in multicommand transactions.") {}
+  SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorModificationInMulticommandTxException)
+};
+
 class ReplicationDisabledOnDiskStorage : public QueryException {
  public:
   ReplicationDisabledOnDiskStorage() : QueryException("Replication is not supported while in on-disk storage mode.") {}
diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp
index c5e4c84c4..57d5398ab 100644
--- a/src/query/frontend/ast/ast.cpp
+++ b/src/query/frontend/ast/ast.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -239,6 +239,9 @@ constexpr utils::TypeInfo query::DumpQuery::kType{utils::TypeId::AST_DUMP_QUERY,
 constexpr utils::TypeInfo query::ReplicationQuery::kType{utils::TypeId::AST_REPLICATION_QUERY, "ReplicationQuery",
                                                          &query::Query::kType};
 
+constexpr utils::TypeInfo query::CoordinatorQuery::kType{utils::TypeId::AST_COORDINATOR_QUERY, "CoordinatorQuery",
+                                                         &query::Query::kType};
+
 constexpr utils::TypeInfo query::LockPathQuery::kType{utils::TypeId::AST_LOCK_PATH_QUERY, "LockPathQuery",
                                                       &query::Query::kType};
 
@@ -293,4 +296,7 @@ constexpr utils::TypeInfo query::ShowDatabasesQuery::kType{utils::TypeId::AST_SH
 constexpr utils::TypeInfo query::EdgeImportModeQuery::kType{utils::TypeId::AST_EDGE_IMPORT_MODE_QUERY,
                                                             "EdgeImportModeQuery", &query::Query::kType};
 
+constexpr utils::TypeInfo query::PatternComprehension::kType{utils::TypeId::AST_PATTERN_COMPREHENSION,
+                                                             "PatternComprehension", &query::Expression::kType};
+
 }  // namespace memgraph
diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp
index 59860d5b0..0cbb790d0 100644
--- a/src/query/frontend/ast/ast.hpp
+++ b/src/query/frontend/ast/ast.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -1209,7 +1209,8 @@ class PropertyLookup : public memgraph::query::Expression {
   }
 
  protected:
-  PropertyLookup(Expression *expression, PropertyIx property) : expression_(expression), property_(property) {}
+  PropertyLookup(Expression *expression, PropertyIx property)
+      : expression_(expression), property_(std::move(property)) {}
 
  private:
   friend class AstStorage;
@@ -1805,9 +1806,9 @@ class EdgeAtom : public memgraph::query::PatternAtom {
   static const utils::TypeInfo kType;
   const utils::TypeInfo &GetTypeInfo() const override { return kType; }
 
-  enum class Type { SINGLE, DEPTH_FIRST, BREADTH_FIRST, WEIGHTED_SHORTEST_PATH, ALL_SHORTEST_PATHS };
+  enum class Type : uint8_t { SINGLE, DEPTH_FIRST, BREADTH_FIRST, WEIGHTED_SHORTEST_PATH, ALL_SHORTEST_PATHS };
 
-  enum class Direction { IN, OUT, BOTH };
+  enum class Direction : uint8_t { IN, OUT, BOTH };
 
   /// Lambda for use in filtering or weight calculation during variable expand.
   struct Lambda {
@@ -2216,7 +2217,7 @@ class IndexQuery : public memgraph::query::Query {
 
  protected:
   IndexQuery(Action action, LabelIx label, std::vector<PropertyIx> properties)
-      : action_(action), label_(label), properties_(properties) {}
+      : action_(action), label_(std::move(label)), properties_(std::move(properties)) {}
 
  private:
   friend class AstStorage;
@@ -2849,6 +2850,7 @@ class AuthQuery : public memgraph::query::Query {
     TRANSACTION_MANAGEMENT,
     MULTI_DATABASE_EDIT,
     MULTI_DATABASE_USE,
+    COORDINATOR
   };
 
   enum class FineGrainedPrivilege { NOTHING, READ, UPDATE, CREATE_DELETE };
@@ -2927,7 +2929,8 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {AuthQuery::Privilege::
                                                           AuthQuery::Privilege::TRANSACTION_MANAGEMENT,
                                                           AuthQuery::Privilege::STORAGE_MODE,
                                                           AuthQuery::Privilege::MULTI_DATABASE_EDIT,
-                                                          AuthQuery::Privilege::MULTI_DATABASE_USE};
+                                                          AuthQuery::Privilege::MULTI_DATABASE_USE,
+                                                          AuthQuery::Privilege::COORDINATOR};
 
 class DatabaseInfoQuery : public memgraph::query::Query {
  public:
@@ -3039,8 +3042,9 @@ class ReplicationQuery : public memgraph::query::Query {
 
   memgraph::query::ReplicationQuery::Action action_;
   memgraph::query::ReplicationQuery::ReplicationRole role_;
-  std::string replica_name_;
+  std::string instance_name_;
   memgraph::query::Expression *socket_address_{nullptr};
+  memgraph::query::Expression *coordinator_socket_address_{nullptr};
   memgraph::query::Expression *port_{nullptr};
   memgraph::query::ReplicationQuery::SyncMode sync_mode_;
 
@@ -3048,10 +3052,53 @@ class ReplicationQuery : public memgraph::query::Query {
     ReplicationQuery *object = storage->Create<ReplicationQuery>();
     object->action_ = action_;
     object->role_ = role_;
-    object->replica_name_ = replica_name_;
+    object->instance_name_ = instance_name_;
     object->socket_address_ = socket_address_ ? socket_address_->Clone(storage) : nullptr;
     object->port_ = port_ ? port_->Clone(storage) : nullptr;
     object->sync_mode_ = sync_mode_;
+    object->coordinator_socket_address_ =
+        coordinator_socket_address_ ? coordinator_socket_address_->Clone(storage) : nullptr;
+
+    return object;
+  }
+
+ private:
+  friend class AstStorage;
+};
+
+class CoordinatorQuery : public memgraph::query::Query {
+ public:
+  static const utils::TypeInfo kType;
+  const utils::TypeInfo &GetTypeInfo() const override { return kType; }
+
+  enum class Action {
+    REGISTER_INSTANCE,
+    SET_INSTANCE_TO_MAIN,
+    SHOW_REPLICATION_CLUSTER,
+  };
+
+  enum class SyncMode { SYNC, ASYNC };
+
+  CoordinatorQuery() = default;
+
+  DEFVISITABLE(QueryVisitor<void>);
+
+  memgraph::query::CoordinatorQuery::Action action_;
+  std::string instance_name_;
+  memgraph::query::Expression *replication_socket_address_{nullptr};
+  memgraph::query::Expression *coordinator_socket_address_{nullptr};
+  memgraph::query::CoordinatorQuery::SyncMode sync_mode_;
+
+  CoordinatorQuery *Clone(AstStorage *storage) const override {
+    auto *object = storage->Create<CoordinatorQuery>();
+    object->action_ = action_;
+    object->instance_name_ = instance_name_;
+    object->replication_socket_address_ =
+        replication_socket_address_ ? replication_socket_address_->Clone(storage) : nullptr;
+    object->sync_mode_ = sync_mode_;
+    object->coordinator_socket_address_ =
+        coordinator_socket_address_ ? coordinator_socket_address_->Clone(storage) : nullptr;
+
     return object;
   }
 
@@ -3520,6 +3567,65 @@ class Exists : public memgraph::query::Expression {
   friend class AstStorage;
 };
 
+class PatternComprehension : public memgraph::query::Expression {
+ public:
+  static const utils::TypeInfo kType;
+  const utils::TypeInfo &GetTypeInfo() const override { return kType; }
+
+  PatternComprehension() = default;
+
+  DEFVISITABLE(ExpressionVisitor<TypedValue>);
+  DEFVISITABLE(ExpressionVisitor<TypedValue *>);
+  DEFVISITABLE(ExpressionVisitor<void>);
+
+  bool Accept(HierarchicalTreeVisitor &visitor) override {
+    if (visitor.PreVisit(*this)) {
+      if (variable_) {
+        variable_->Accept(visitor);
+      }
+      pattern_->Accept(visitor);
+      if (filter_) {
+        filter_->Accept(visitor);
+      }
+      resultExpr_->Accept(visitor);
+    }
+    return visitor.PostVisit(*this);
+  }
+
+  PatternComprehension *MapTo(const Symbol &symbol) {
+    symbol_pos_ = symbol.position();
+    return this;
+  }
+
+  // The variable name.
+  Identifier *variable_{nullptr};
+  // The pattern to match.
+  Pattern *pattern_{nullptr};
+  // Optional WHERE clause for filtering.
+  Where *filter_{nullptr};
+  // The projection expression.
+  Expression *resultExpr_{nullptr};
+
+  /// Symbol table position of the symbol this Aggregation is mapped to.
+  int32_t symbol_pos_{-1};
+
+  PatternComprehension *Clone(AstStorage *storage) const override {
+    PatternComprehension *object = storage->Create<PatternComprehension>();
+    object->pattern_ = pattern_ ? pattern_->Clone(storage) : nullptr;
+    object->filter_ = filter_ ? filter_->Clone(storage) : nullptr;
+    object->resultExpr_ = resultExpr_ ? resultExpr_->Clone(storage) : nullptr;
+
+    object->symbol_pos_ = symbol_pos_;
+    return object;
+  }
+
+ protected:
+  PatternComprehension(Identifier *variable, Pattern *pattern) : variable_(variable), pattern_(pattern) {}
+
+ private:
+  friend class AstStorage;
+};
+
 class CallSubquery : public memgraph::query::Clause {
  public:
   static const utils::TypeInfo kType;
@@ -3553,7 +3659,7 @@ class MultiDatabaseQuery : public memgraph::query::Query {
 
   DEFVISITABLE(QueryVisitor<void>);
 
-  enum class Action { CREATE, USE, DROP };
+  enum class Action { CREATE, USE, DROP, SHOW };
 
   memgraph::query::MultiDatabaseQuery::Action action_;
   std::string db_name_;
diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp
index 793c15a95..5d463d3ee 100644
--- a/src/query/frontend/ast/ast_visitor.hpp
+++ b/src/query/frontend/ast/ast_visitor.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -107,6 +107,8 @@ class Exists;
 class MultiDatabaseQuery;
 class ShowDatabasesQuery;
 class EdgeImportModeQuery;
+class PatternComprehension;
+class CoordinatorQuery;
 
 using TreeCompositeVisitor = utils::CompositeVisitor<
     SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
@@ -116,7 +118,7 @@ using TreeCompositeVisitor = utils::CompositeVisitor<
     MapProjectionLiteral, PropertyLookup, AllPropertiesLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce,
     Extract, All, Single, Any, None, CallProcedure, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete,
     Where, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv,
-    Foreach, Exists, CallSubquery, CypherQuery>;
+    Foreach, Exists, CallSubquery, CypherQuery, PatternComprehension>;
 
 using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
 
@@ -137,7 +139,7 @@ class ExpressionVisitor
                             ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator,
                             ListLiteral, MapLiteral, MapProjectionLiteral, PropertyLookup, AllPropertiesLookup,
                             LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None,
-                            ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists> {};
+                            ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists, PatternComprehension> {};
 
 template <class TResult>
 class QueryVisitor
@@ -145,6 +147,7 @@ class QueryVisitor
                             SystemInfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
                             FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery,
                             SettingQuery, VersionQuery, ShowConfigQuery, TransactionQueueQuery, StorageModeQuery,
-                            AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery> {};
+                            AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery,
+                            CoordinatorQuery> {};
 
 }  // namespace memgraph::query
diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp
index 7002ee4b9..5735326ac 100644
--- a/src/query/frontend/ast/cypher_main_visitor.cpp
+++ b/src/query/frontend/ast/cypher_main_visitor.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -302,6 +302,13 @@ antlrcpp::Any CypherMainVisitor::visitReplicationQuery(MemgraphCypher::Replicati
   return replication_query;
 }
 
+antlrcpp::Any CypherMainVisitor::visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) {
+  MG_ASSERT(ctx->children.size() == 1, "CoordinatorQuery should have exactly one child!");
+  auto *coordinator_query = std::any_cast<CoordinatorQuery *>(ctx->children[0]->accept(this));
+  query_ = coordinator_query;
+  return coordinator_query;
+}
+
 antlrcpp::Any CypherMainVisitor::visitEdgeImportModeQuery(MemgraphCypher::EdgeImportModeQueryContext *ctx) {
   auto *edge_import_mode_query = storage_->Create<EdgeImportModeQuery>();
   if (ctx->ACTIVE()) {
@@ -316,24 +323,34 @@ antlrcpp::Any CypherMainVisitor::visitEdgeImportModeQuery(MemgraphCypher::EdgeIm
 antlrcpp::Any CypherMainVisitor::visitSetReplicationRole(MemgraphCypher::SetReplicationRoleContext *ctx) {
   auto *replication_query = storage_->Create<ReplicationQuery>();
   replication_query->action_ = ReplicationQuery::Action::SET_REPLICATION_ROLE;
+
+  auto set_replication_port = [replication_query, ctx, this]() -> void {
+    if (ctx->port->numberLiteral() && ctx->port->numberLiteral()->integerLiteral()) {
+      replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this));
+    } else {
+      throw SyntaxException("Port must be an integer literal!");
+    }
+  };
+
   if (ctx->MAIN()) {
+    replication_query->role_ = ReplicationQuery::ReplicationRole::MAIN;
     if (ctx->WITH() || ctx->PORT()) {
       throw SemanticException("Main can't set a port!");
     }
-    replication_query->role_ = ReplicationQuery::ReplicationRole::MAIN;
+
   } else if (ctx->REPLICA()) {
     replication_query->role_ = ReplicationQuery::ReplicationRole::REPLICA;
     if (ctx->WITH() && ctx->PORT()) {
-      if (ctx->port->numberLiteral() && ctx->port->numberLiteral()->integerLiteral()) {
-        replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this));
-      } else {
-        throw SyntaxException("Port must be an integer literal!");
-      }
+      set_replication_port();
+    } else {
+      throw SemanticException("Replica must set a port!");
     }
   }
+
   return replication_query;
 }
-antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext *ctx) {
+
+antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext * /*ctx*/) {
   auto *replication_query = storage_->Create<ReplicationQuery>();
   replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICATION_ROLE;
   return replication_query;
@@ -342,7 +359,7 @@ antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowRe
 antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterReplicaContext *ctx) {
   auto *replication_query = storage_->Create<ReplicationQuery>();
   replication_query->action_ = ReplicationQuery::Action::REGISTER_REPLICA;
-  replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this));
+  replication_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
   if (ctx->SYNC()) {
     replication_query->sync_mode_ = memgraph::query::ReplicationQuery::SyncMode::SYNC;
   } else if (ctx->ASYNC()) {
@@ -351,26 +368,67 @@ antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterRe
 
   if (!ctx->socketAddress()->literal()->StringLiteral()) {
     throw SemanticException("Socket address should be a string literal!");
-  } else {
-    replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this));
   }
+  replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this));
 
   return replication_query;
 }
 
+// License check is done in the interpreter.
+antlrcpp::Any CypherMainVisitor::visitRegisterInstanceOnCoordinator(
+    MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) {
+  auto *coordinator_query = storage_->Create<CoordinatorQuery>();
+  if (!ctx->replicationSocketAddress()->literal()->StringLiteral()) {
+    throw SemanticException("Replication socket address should be a string literal!");
+  }
+
+  if (!ctx->coordinatorSocketAddress()->literal()->StringLiteral()) {
+    throw SemanticException("Coordinator socket address should be a string literal!");
+  }
+  coordinator_query->action_ = CoordinatorQuery::Action::REGISTER_INSTANCE;
+  coordinator_query->replication_socket_address_ =
+      std::any_cast<Expression *>(ctx->replicationSocketAddress()->accept(this));
+  coordinator_query->coordinator_socket_address_ =
+      std::any_cast<Expression *>(ctx->coordinatorSocketAddress()->accept(this));
+  coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
+  if (ctx->ASYNC()) {
+    coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::ASYNC;
+  } else {
+    coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::SYNC;
+  }
+
+  return coordinator_query;
+}
+
+// License check is done in the interpreter
+antlrcpp::Any CypherMainVisitor::visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext * /*ctx*/) {
+  auto *coordinator_query = storage_->Create<CoordinatorQuery>();
+  coordinator_query->action_ = CoordinatorQuery::Action::SHOW_REPLICATION_CLUSTER;
+  return coordinator_query;
+}
+
 antlrcpp::Any CypherMainVisitor::visitDropReplica(MemgraphCypher::DropReplicaContext *ctx) {
   auto *replication_query = storage_->Create<ReplicationQuery>();
   replication_query->action_ = ReplicationQuery::Action::DROP_REPLICA;
-  replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this));
+  replication_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
   return replication_query;
 }
 
-antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) {
+antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasContext * /*ctx*/) {
   auto *replication_query = storage_->Create<ReplicationQuery>();
   replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICAS;
   return replication_query;
 }
 
+// License check is done in the interpreter
+antlrcpp::Any CypherMainVisitor::visitSetInstanceToMain(MemgraphCypher::SetInstanceToMainContext *ctx) {
+  auto *coordinator_query = storage_->Create<CoordinatorQuery>();
+  coordinator_query->action_ = CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN;
+  coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
+  query_ = coordinator_query;
+  return coordinator_query;
+}
+
 antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) {
   auto *lock_query = storage_->Create<LockPathQuery>();
   if (ctx->STATUS()) {
@@ -1638,6 +1696,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
   if (ctx->STORAGE_MODE()) return AuthQuery::Privilege::STORAGE_MODE;
   if (ctx->MULTI_DATABASE_EDIT()) return AuthQuery::Privilege::MULTI_DATABASE_EDIT;
   if (ctx->MULTI_DATABASE_USE()) return AuthQuery::Privilege::MULTI_DATABASE_USE;
+  if (ctx->COORDINATOR()) return AuthQuery::Privilege::COORDINATOR;
   LOG_FATAL("Should not get here - unknown privilege!");
 }
 
@@ -1752,7 +1811,11 @@ antlrcpp::Any CypherMainVisitor::visitReturnBody(MemgraphCypher::ReturnBodyConte
     body.skip = static_cast<Expression *>(std::any_cast<Expression *>(ctx->skip()->accept(this)));
   }
   if (ctx->limit()) {
-    body.limit = static_cast<Expression *>(std::any_cast<Expression *>(ctx->limit()->accept(this)));
+    if (ctx->limit()->expression()) {
+      body.limit = std::any_cast<Expression *>(ctx->limit()->accept(this));
+    } else {
+      body.limit = std::any_cast<ParameterLookup *>(ctx->limit()->accept(this));
+    }
   }
   std::tie(body.all_identifiers, body.named_expressions) =
       std::any_cast<std::pair<bool, std::vector<NamedExpression *>>>(ctx->returnItems()->accept(this));
@@ -1825,7 +1888,15 @@ antlrcpp::Any CypherMainVisitor::visitNodePattern(MemgraphCypher::NodePatternCon
 antlrcpp::Any CypherMainVisitor::visitNodeLabels(MemgraphCypher::NodeLabelsContext *ctx) {
   std::vector<LabelIx> labels;
   for (auto *node_label : ctx->nodeLabel()) {
-    labels.push_back(AddLabel(std::any_cast<std::string>(node_label->accept(this))));
+    if (node_label->labelName()->symbolicName()) {
+      labels.emplace_back(AddLabel(std::any_cast<std::string>(node_label->accept(this))));
+    } else {
+      // If we have a parameter, we have to resolve it.
+      const auto *param_lookup = std::any_cast<ParameterLookup *>(node_label->accept(this));
+      const auto label_name = parameters_->AtTokenPosition(param_lookup->token_position_).ValueString();
+      labels.emplace_back(storage_->GetLabelIx(label_name));
+      query_info_.is_cacheable = false;  // We can't cache queries with label parameters.
+    }
   }
   return labels;
 }
@@ -1961,6 +2032,18 @@ antlrcpp::Any CypherMainVisitor::visitPatternElement(MemgraphCypher::PatternElem
   return pattern;
 }
 
+antlrcpp::Any CypherMainVisitor::visitRelationshipsPattern(MemgraphCypher::RelationshipsPatternContext *ctx) {
+  auto *pattern = storage_->Create<Pattern>();
+  pattern->atoms_.push_back(std::any_cast<NodeAtom *>(ctx->nodePattern()->accept(this)));
+  for (auto *pattern_element_chain : ctx->patternElementChain()) {
+    auto element = std::any_cast<std::pair<PatternAtom *, PatternAtom *>>(pattern_element_chain->accept(this));
+    pattern->atoms_.push_back(element.first);
+    pattern->atoms_.push_back(element.second);
+  }
+  anonymous_identifiers.push_back(&pattern->identifier_);
+  return pattern;
+}
+
 antlrcpp::Any CypherMainVisitor::visitPatternElementChain(MemgraphCypher::PatternElementChainContext *ctx) {
   return std::pair<PatternAtom *, PatternAtom *>(std::any_cast<EdgeAtom *>(ctx->relationshipPattern()->accept(this)),
                                                  std::any_cast<NodeAtom *>(ctx->nodePattern()->accept(this)));
@@ -2455,6 +2538,8 @@ antlrcpp::Any CypherMainVisitor::visitAtom(MemgraphCypher::AtomContext *ctx) {
     return static_cast<Expression *>(storage_->Create<Extract>(ident, list, expr));
   } else if (ctx->existsExpression()) {
     return std::any_cast<Expression *>(ctx->existsExpression()->accept(this));
+  } else if (ctx->patternComprehension()) {
+    return std::any_cast<Expression *>(ctx->patternComprehension()->accept(this));
   }
 
   // TODO: Implement this. We don't support comprehensions, filtering... at
@@ -2515,6 +2600,19 @@ antlrcpp::Any CypherMainVisitor::visitExistsExpression(MemgraphCypher::ExistsExp
   return static_cast<Expression *>(exists);
 }
 
+antlrcpp::Any CypherMainVisitor::visitPatternComprehension(MemgraphCypher::PatternComprehensionContext *ctx) {
+  auto *comprehension = storage_->Create<PatternComprehension>();
+  if (ctx->variable()) {
+    comprehension->variable_ = storage_->Create<Identifier>(std::any_cast<std::string>(ctx->variable()->accept(this)));
+  }
+  comprehension->pattern_ = std::any_cast<Pattern *>(ctx->relationshipsPattern()->accept(this));
+  if (ctx->where()) {
+    comprehension->filter_ = std::any_cast<Where *>(ctx->where()->accept(this));
+  }
+  comprehension->resultExpr_ = std::any_cast<Expression *>(ctx->expression()->accept(this));
+  return static_cast<Expression *>(comprehension);
+}
+
 antlrcpp::Any CypherMainVisitor::visitParenthesizedExpression(MemgraphCypher::ParenthesizedExpressionContext *ctx) {
   return std::any_cast<Expression *>(ctx->expression()->accept(this));
 }
@@ -2853,6 +2951,14 @@ antlrcpp::Any CypherMainVisitor::visitDropDatabase(MemgraphCypher::DropDatabaseC
   return mdb_query;
 }
 
+antlrcpp::Any CypherMainVisitor::visitShowDatabase(MemgraphCypher::ShowDatabaseContext * /*ctx*/) {
+  auto *mdb_query = storage_->Create<MultiDatabaseQuery>();
+  mdb_query->db_name_ = "";
+  mdb_query->action_ = MultiDatabaseQuery::Action::SHOW;
+  query_ = mdb_query;
+  return mdb_query;
+}
+
 antlrcpp::Any CypherMainVisitor::visitShowDatabases(MemgraphCypher::ShowDatabasesContext * /*ctx*/) {
   query_ = storage_->Create<ShowDatabasesQuery>();
   return query_;
diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp
index 9e828674f..e9da98f71 100644
--- a/src/query/frontend/ast/cypher_main_visitor.hpp
+++ b/src/query/frontend/ast/cypher_main_visitor.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 
 #include "query/frontend/ast/ast.hpp"
 #include "query/frontend/opencypher/generated/MemgraphCypherBaseVisitor.h"
+#include "query/parameters.hpp"
 #include "utils/exceptions.hpp"
 #include "utils/logging.hpp"
 
@@ -30,7 +31,8 @@ struct ParsingContext {
 
 class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
  public:
-  explicit CypherMainVisitor(ParsingContext context, AstStorage *storage) : context_(context), storage_(storage) {}
+  explicit CypherMainVisitor(ParsingContext context, AstStorage *storage, Parameters *parameters)
+      : context_(context), storage_(storage), parameters_(parameters) {}
 
  private:
   Expression *CreateBinaryOperatorByToken(size_t token, Expression *e1, Expression *e2) {
@@ -231,6 +233,26 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
    */
   antlrcpp::Any visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) override;
 
+  /**
+   * @return CoordinatorQuery*
+   */
+  antlrcpp::Any visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) override;
+
+  /**
+   * @return CoordinatorQuery*
+   */
+  antlrcpp::Any visitRegisterInstanceOnCoordinator(MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) override;
+
+  /**
+   * @return CoordinatorQuery*
+   */
+  antlrcpp::Any visitSetInstanceToMain(MemgraphCypher::SetInstanceToMainContext *ctx) override;
+
+  /**
+   * @return CoordinatorQuery*
+   */
+  antlrcpp::Any visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext *ctx) override;
+
   /**
    * @return LockPathQuery*
    */
@@ -676,6 +698,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
    */
   antlrcpp::Any visitPatternElement(MemgraphCypher::PatternElementContext *ctx) override;
 
+  /**
+   * @return Pattern*
+   */
+  antlrcpp::Any visitRelationshipsPattern(MemgraphCypher::RelationshipsPatternContext *ctx) override;
+
   /**
    * @return vector<pair<EdgeAtom*, NodeAtom*>>
    */
@@ -841,6 +868,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
    */
   antlrcpp::Any visitExistsExpression(MemgraphCypher::ExistsExpressionContext *ctx) override;
 
+  /**
+   * @return pattern comprehension (Expression)
+   */
+  antlrcpp::Any visitPatternComprehension(MemgraphCypher::PatternComprehensionContext *ctx) override;
+
   /**
    * @return Expression*
    */
@@ -985,6 +1017,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
    */
   antlrcpp::Any visitDropDatabase(MemgraphCypher::DropDatabaseContext *ctx) override;
 
+  /**
+   * @return MultiDatabaseQuery*
+   */
+  antlrcpp::Any visitShowDatabase(MemgraphCypher::ShowDatabaseContext *ctx) override;
+
   /**
    * @return ShowDatabasesQuery*
    */
@@ -1022,6 +1059,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
   // return.
   bool in_with_ = false;
 
+  Parameters *parameters_;
+
   QueryInfo query_info_;
 };
 }  // namespace memgraph::query::frontend
diff --git a/src/query/frontend/ast/pretty_print.cpp b/src/query/frontend/ast/pretty_print.cpp
index ef45afd7d..61bd23797 100644
--- a/src/query/frontend/ast/pretty_print.cpp
+++ b/src/query/frontend/ast/pretty_print.cpp
@@ -73,6 +73,7 @@ class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
   void Visit(ParameterLookup &op) override;
   void Visit(NamedExpression &op) override;
   void Visit(RegexMatch &op) override;
+  void Visit(PatternComprehension &op) override;
 
  private:
   std::ostream *out_;
@@ -323,6 +324,10 @@ void ExpressionPrettyPrinter::Visit(NamedExpression &op) {
 
 void ExpressionPrettyPrinter::Visit(RegexMatch &op) { PrintOperator(out_, "=~", op.string_expr_, op.regex_); }
 
+void ExpressionPrettyPrinter::Visit(PatternComprehension &op) {
+  PrintOperator(out_, "Pattern Comprehension", op.variable_, op.pattern_, op.filter_, op.resultExpr_);
+}
+
 }  // namespace
 
 void PrintExpression(Expression *expr, std::ostream *out) {
diff --git a/src/query/frontend/opencypher/grammar/Cypher.g4 b/src/query/frontend/opencypher/grammar/Cypher.g4
index d387002d8..55cb53ef3 100644
--- a/src/query/frontend/opencypher/grammar/Cypher.g4
+++ b/src/query/frontend/opencypher/grammar/Cypher.g4
@@ -143,7 +143,7 @@ order : ORDER BY sortItem ( ',' sortItem )* ;
 
 skip : L_SKIP expression ;
 
-limit : LIMIT expression ;
+limit : LIMIT ( expression | parameter ) ;
 
 sortItem : expression ( ASCENDING | ASC | DESCENDING | DESC )? ;
 
@@ -193,7 +193,7 @@ nodeLabels : nodeLabel ( nodeLabel )* ;
 
 nodeLabel : ':' labelName ;
 
-labelName : symbolicName ;
+labelName : symbolicName | parameter;
 
 relTypeName : symbolicName ;
 
@@ -296,7 +296,7 @@ functionName : symbolicName ( '.' symbolicName )* ;
 
 listComprehension : '[' filterExpression ( '|' expression )? ']' ;
 
-patternComprehension : '[' ( variable '=' )? relationshipsPattern ( WHERE expression )? '|' expression ']' ;
+patternComprehension : '[' ( variable '=' )? relationshipsPattern ( where )? '|' resultExpr=expression ']' ;
 
 propertyLookup : '.' ( propertyKeyName ) ;
 
diff --git a/src/query/frontend/opencypher/grammar/CypherLexer.g4 b/src/query/frontend/opencypher/grammar/CypherLexer.g4
index 3428a2191..3e3c640d6 100644
--- a/src/query/frontend/opencypher/grammar/CypherLexer.g4
+++ b/src/query/frontend/opencypher/grammar/CypherLexer.g4
@@ -102,6 +102,7 @@ FILTER         : F I L T E R ;
 IN             : I N ;
 INDEX          : I N D E X ;
 INFO           : I N F O ;
+INSTANCE       : I N S T A N C E ;
 IS             : I S ;
 KB             : K B ;
 KEY            : K E Y ;
@@ -122,6 +123,7 @@ PROCEDURE      : P R O C E D U R E ;
 PROFILE        : P R O F I L E ;
 QUERY          : Q U E R Y ;
 REDUCE         : R E D U C E ;
+REGISTER       : R E G I S T E R;
 REMOVE         : R E M O V E ;
 RETURN         : R E T U R N ;
 SET            : S E T ;
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
index d585acbb1..e41184468 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
@@ -48,10 +48,12 @@ memgraphCypherKeyword : cypherKeyword
                       | DATABASE
                       | DENY
                       | DROP
+                      | DO
                       | DUMP
                       | EDGE
                       | EDGE_TYPES
                       | EXECUTE
+                      | FAILOVER
                       | FOR
                       | FOREACH
                       | FREE
@@ -61,6 +63,7 @@ memgraphCypherKeyword : cypherKeyword
                       | GRANT
                       | HEADER
                       | IDENTIFIED
+                      | INSTANCE
                       | NODE_LABELS
                       | NULLIF
                       | IMPORT
@@ -151,6 +154,7 @@ query : cypherQuery
       | multiDatabaseQuery
       | showDatabases
       | edgeImportModeQuery
+      | coordinatorQuery
       ;
 
 cypherQuery : ( indexHints )? singleQuery ( cypherUnion )* ( queryMemoryLimit )? ;
@@ -183,6 +187,11 @@ replicationQuery : setReplicationRole
                  | showReplicas
                  ;
 
+coordinatorQuery : registerInstanceOnCoordinator
+                 | setInstanceToMain
+                 | showReplicationCluster
+                 ;
+
 triggerQuery : createTrigger
              | dropTrigger
              | showTriggers
@@ -323,6 +332,7 @@ privilege : CREATE
           | STORAGE_MODE
           | MULTI_DATABASE_EDIT
           | MULTI_DATABASE_USE
+          | COORDINATOR
           ;
 
 granularPrivilege : NOTHING | READ | UPDATE | CREATE_DELETE ;
@@ -364,14 +374,23 @@ setReplicationRole : SET REPLICATION ROLE TO ( MAIN | REPLICA )
 
 showReplicationRole : SHOW REPLICATION ROLE ;
 
-replicaName : symbolicName ;
+showReplicationCluster : SHOW REPLICATION CLUSTER ;
+
+instanceName : symbolicName ;
 
 socketAddress : literal ;
 
-registerReplica : REGISTER REPLICA replicaName ( SYNC | ASYNC )
+coordinatorSocketAddress : literal ;
+replicationSocketAddress : literal ;
+
+registerReplica : REGISTER REPLICA instanceName ( SYNC | ASYNC )
                 TO socketAddress ;
 
-dropReplica : DROP REPLICA replicaName ;
+registerInstanceOnCoordinator : REGISTER INSTANCE  instanceName ON coordinatorSocketAddress ( AS ASYNC ) ?  WITH  replicationSocketAddress ;
+
+setInstanceToMain : SET INSTANCE instanceName TO MAIN ;
+
+dropReplica : DROP REPLICA instanceName ;
 
 showReplicas : SHOW REPLICAS ;
 
@@ -480,6 +499,7 @@ transactionId : literal ;
 multiDatabaseQuery : createDatabase
                    | useDatabase
                    | dropDatabase
+                   | showDatabase
                    ;
 
 createDatabase : CREATE DATABASE databaseName ;
@@ -488,6 +508,8 @@ useDatabase : USE DATABASE databaseName ;
 
 dropDatabase : DROP DATABASE databaseName ;
 
+showDatabase : SHOW DATABASE ;
+
 showDatabases : SHOW DATABASES ;
 
 edgeImportModeQuery : EDGE IMPORT MODE ( ACTIVE | INACTIVE ) ;
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
index 1b44a6e79..b0febc4af 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
@@ -39,15 +39,18 @@ BOOTSTRAP_SERVERS       : B O O T S T R A P UNDERSCORE S E R V E R S ;
 CALL                    : C A L L ;
 CHECK                   : C H E C K ;
 CLEAR                   : C L E A R ;
+CLUSTER                 : C L U S T E R ;
 COMMIT                  : C O M M I T ;
 COMMITTED               : C O M M I T T E D ;
 CONFIG                  : C O N F I G ;
 CONFIGS                 : C O N F I G S;
 CONSUMER_GROUP          : C O N S U M E R UNDERSCORE G R O U P ;
+COORDINATOR             : C O O R D I N A T O R ;
 CREATE_DELETE           : C R E A T E UNDERSCORE D E L E T E ;
 CREDENTIALS             : C R E D E N T I A L S ;
 CSV                     : C S V ;
 DATA                    : D A T A ;
+DO                      : D O ;
 DELIMITER               : D E L I M I T E R ;
 DATABASE                : D A T A B A S E ;
 DATABASES               : D A T A B A S E S ;
@@ -59,6 +62,7 @@ DURABILITY              : D U R A B I L I T Y ;
 EDGE                    : E D G E ;
 EDGE_TYPES              : E D G E UNDERSCORE T Y P E S ;
 EXECUTE                 : E X E C U T E ;
+FAILOVER                : F A I L O V E R ;
 FOR                     : F O R ;
 FOREACH                 : F O R E A C H;
 FREE                    : F R E E ;
@@ -75,6 +79,7 @@ IMPORT                  : I M P O R T ;
 INACTIVE                : I N A C T I V E ;
 IN_MEMORY_ANALYTICAL    : I N UNDERSCORE M E M O R Y UNDERSCORE A N A L Y T I C A L ;
 IN_MEMORY_TRANSACTIONAL : I N UNDERSCORE M E M O R Y UNDERSCORE T R A N S A C T I O N A L ;
+INSTANCE                : I N S T A N C E ;
 ISOLATION               : I S O L A T I O N ;
 KAFKA                   : K A F K A ;
 LABELS                  : L A B E L S ;
@@ -107,6 +112,7 @@ REVOKE                  : R E V O K E ;
 ROLE                    : R O L E ;
 ROLES                   : R O L E S ;
 QUOTE                   : Q U O T E ;
+SERVER                  : S E R V E R ;
 SERVICE_URL             : S E R V I C E UNDERSCORE U R L ;
 SESSION                 : S E S S I O N ;
 SETTING                 : S E T T I N G ;
diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp
index 04772cded..ef66a75ac 100644
--- a/src/query/frontend/semantic/required_privileges.cpp
+++ b/src/query/frontend/semantic/required_privileges.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -106,6 +106,7 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
         AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_EDIT);
         break;
       case MultiDatabaseQuery::Action::USE:
+      case MultiDatabaseQuery::Action::SHOW:
         AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_USE);
         break;
     }
@@ -115,6 +116,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
     AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_USE); /* OR EDIT */
   }
 
+  void Visit(CoordinatorQuery & /*coordinator_query*/) override { AddPrivilege(AuthQuery::Privilege::COORDINATOR); }
+
   bool PreVisit(Create & /*unused*/) override {
     AddPrivilege(AuthQuery::Privilege::CREATE);
     return false;
diff --git a/src/query/frontend/semantic/symbol_generator.hpp b/src/query/frontend/semantic/symbol_generator.hpp
index 207bbddbd..f9e6468f6 100644
--- a/src/query/frontend/semantic/symbol_generator.hpp
+++ b/src/query/frontend/semantic/symbol_generator.hpp
@@ -249,6 +249,7 @@ class PropertyLookupEvaluationModeVisitor : public ExpressionVisitor<void> {
   void Visit(ParameterLookup &op) override{};
   void Visit(NamedExpression &op) override { op.expression_->Accept(*this); };
   void Visit(RegexMatch &op) override{};
+  void Visit(PatternComprehension &op) override{};
 
   void Visit(PropertyLookup & /*property_lookup*/) override;
 
diff --git a/src/query/frontend/semantic/symbol_table.hpp b/src/query/frontend/semantic/symbol_table.hpp
index 0b521356c..cf462c437 100644
--- a/src/query/frontend/semantic/symbol_table.hpp
+++ b/src/query/frontend/semantic/symbol_table.hpp
@@ -52,6 +52,9 @@ class SymbolTable final {
   const Symbol &at(const NamedExpression &nexpr) const { return table_.at(nexpr.symbol_pos_); }
   const Symbol &at(const Aggregation &aggr) const { return table_.at(aggr.symbol_pos_); }
   const Symbol &at(const Exists &exists) const { return table_.at(exists.symbol_pos_); }
+  const Symbol &at(const PatternComprehension &pattern_comprehension) const {
+    return table_.at(pattern_comprehension.symbol_pos_);
+  }
 
   // TODO: Remove these since members are public
   int32_t max_position() const { return static_cast<int32_t>(table_.size()); }
diff --git a/src/query/frontend/stripped_lexer_constants.hpp b/src/query/frontend/stripped_lexer_constants.hpp
index 21a14ae83..bd6ab7971 100644
--- a/src/query/frontend/stripped_lexer_constants.hpp
+++ b/src/query/frontend/stripped_lexer_constants.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -218,7 +218,8 @@ const trie::Trie kKeywords = {"union",
                               "directory",
                               "lock",
                               "unlock",
-                              "build"};
+                              "build",
+                              "instance"};
 
 // Unicode codepoints that are allowed at the start of the unescaped name.
 const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp
index 6f49ee99f..ece0aec78 100644
--- a/src/query/interpret/awesome_memgraph_functions.cpp
+++ b/src/query/interpret/awesome_memgraph_functions.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -504,8 +504,8 @@ TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContex
     return TypedValue(value.ValueInt() != 0L, ctx.memory);
   } else {
     auto s = utils::ToUpperCase(utils::Trim(value.ValueString()));
-    if (s == "TRUE") return TypedValue(true, ctx.memory);
-    if (s == "FALSE") return TypedValue(false, ctx.memory);
+    if (s == "TRUE" || s == "T") return TypedValue(true, ctx.memory);
+    if (s == "FALSE" || s == "F") return TypedValue(false, ctx.memory);
     // I think this is just stupid and that exception should be thrown, but
     // neo4j does it this way...
     return TypedValue(ctx.memory);
@@ -957,7 +957,7 @@ TypedValue ToString(const TypedValue *args, int64_t nargs, const FunctionContext
     return TypedValue(std::to_string(arg.ValueInt()), ctx.memory);
   }
   if (arg.IsDouble()) {
-    return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory);
+    return TypedValue(memgraph::utils::DoubleToString(arg.ValueDouble()), ctx.memory);
   }
   if (arg.IsDate()) {
     return TypedValue(arg.ValueDate().ToString(), ctx.memory);
diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp
index f4f3126cd..fe47a3fcd 100644
--- a/src/query/interpret/eval.hpp
+++ b/src/query/interpret/eval.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -101,6 +101,7 @@ class ReferenceExpressionEvaluator : public ExpressionVisitor<TypedValue *> {
   UNSUCCESSFUL_VISIT(ParameterLookup);
   UNSUCCESSFUL_VISIT(RegexMatch);
   UNSUCCESSFUL_VISIT(Exists);
+  UNSUCCESSFUL_VISIT(PatternComprehension);
 
 #undef UNSUCCESSFUL_VISIT
 
@@ -170,6 +171,7 @@ class PrimitiveLiteralExpressionEvaluator : public ExpressionVisitor<TypedValue>
   INVALID_VISIT(Identifier)
   INVALID_VISIT(RegexMatch)
   INVALID_VISIT(Exists)
+  INVALID_VISIT(PatternComprehension)
 
 #undef INVALID_VISIT
  private:
@@ -1090,6 +1092,10 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
     }
   }
 
+  TypedValue Visit(PatternComprehension & /*pattern_comprehension*/) override {
+    throw utils::NotYetImplemented("Expression evaluator can not handle pattern comprehension.");
+  }
+
  private:
   template <class TRecordAccessor>
   std::map<storage::PropertyId, storage::PropertyValue> GetAllProperties(const TRecordAccessor &record_accessor) {
@@ -1115,11 +1121,11 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
           throw QueryRuntimeException("Unexpected error when getting properties.");
       }
     }
-    return *maybe_props;
+    return *std::move(maybe_props);
   }
 
   template <class TRecordAccessor>
-  storage::PropertyValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
+  storage::PropertyValue GetProperty(const TRecordAccessor &record_accessor, const PropertyIx &prop) {
     auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]);
     if (maybe_prop.HasError() && maybe_prop.GetError() == storage::Error::NONEXISTENT_OBJECT) {
       // This is a very nasty and temporary hack in order to make MERGE work.
@@ -1142,7 +1148,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
           throw QueryRuntimeException("Unexpected error when getting a property.");
       }
     }
-    return *maybe_prop;
+    return *std::move(maybe_prop);
   }
 
   template <class TRecordAccessor>
@@ -1172,7 +1178,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
     return *maybe_prop;
   }
 
-  storage::LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
+  storage::LabelId GetLabel(const LabelIx &label) { return ctx_->labels[label.ix]; }
 
   Frame *frame_;
   const SymbolTable *symbol_table_;
diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp
index 7292c4591..1576df7c4 100644
--- a/src/query/interpreter.cpp
+++ b/src/query/interpreter.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -35,9 +35,9 @@
 #include "auth/models.hpp"
 #include "csv/parsing.hpp"
 #include "dbms/database.hpp"
-#include "dbms/dbms_handler.hpp"
 #include "dbms/global.hpp"
 #include "dbms/inmemory/storage_helper.hpp"
+#include "flags/replication.hpp"
 #include "flags/run_time_configurable.hpp"
 #include "glue/communication.hpp"
 #include "license/license.hpp"
@@ -101,12 +101,17 @@
 #include "utils/typeinfo.hpp"
 #include "utils/variant_helpers.hpp"
 
+#include "dbms/coordinator_handler.hpp"
 #include "dbms/dbms_handler.hpp"
 #include "dbms/replication_handler.hpp"
 #include "query/auth_query_handler.hpp"
 #include "query/interpreter_context.hpp"
 #include "replication/state.hpp"
 
+#ifdef MG_ENTERPRISE
+#include "coordination/constants.hpp"
+#endif
+
 namespace memgraph::metrics {
 extern Event ReadQuery;
 extern Event WriteQuery;
@@ -121,6 +126,7 @@ extern const Event CommitedTransactions;
 extern const Event RollbackedTransactions;
 extern const Event ActiveTransactions;
 }  // namespace memgraph::metrics
+
 void memgraph::query::CurrentDB::SetupDatabaseTransaction(
     std::optional<storage::IsolationLevel> override_isolation_level, bool could_commit, bool unique) {
   auto &db_acc = *db_acc_;
@@ -148,6 +154,7 @@ void memgraph::query::CurrentDB::CleanupDBTransaction(bool abort) {
 namespace memgraph::query {
 
 constexpr std::string_view kSchemaAssert = "SCHEMA.ASSERT";
+constexpr int kSystemTxTryMS = 100;  //!< Duration of the unique try_lock_for
 
 template <typename>
 constexpr auto kAlwaysFalse = false;
@@ -259,33 +266,61 @@ bool IsAllShortestPathsQuery(const std::vector<memgraph::query::Clause *> &claus
   return false;
 }
 
-inline auto convertToReplicationMode(const ReplicationQuery::SyncMode &sync_mode) -> replication::ReplicationMode {
+inline auto convertFromCoordinatorToReplicationMode(const CoordinatorQuery::SyncMode &sync_mode)
+    -> replication_coordination_glue::ReplicationMode {
   switch (sync_mode) {
-    case ReplicationQuery::SyncMode::ASYNC: {
-      return replication::ReplicationMode::ASYNC;
+    case CoordinatorQuery::SyncMode::ASYNC: {
+      return replication_coordination_glue::ReplicationMode::ASYNC;
     }
-    case ReplicationQuery::SyncMode::SYNC: {
-      return replication::ReplicationMode::SYNC;
+    case CoordinatorQuery::SyncMode::SYNC: {
+      return replication_coordination_glue::ReplicationMode::SYNC;
     }
   }
   // TODO: C++23 std::unreachable()
-  return replication::ReplicationMode::ASYNC;
+  return replication_coordination_glue::ReplicationMode::ASYNC;
 }
 
-class ReplQueryHandler final : public query::ReplicationQueryHandler {
+inline auto convertToReplicationMode(const ReplicationQuery::SyncMode &sync_mode)
+    -> replication_coordination_glue::ReplicationMode {
+  switch (sync_mode) {
+    case ReplicationQuery::SyncMode::ASYNC: {
+      return replication_coordination_glue::ReplicationMode::ASYNC;
+    }
+    case ReplicationQuery::SyncMode::SYNC: {
+      return replication_coordination_glue::ReplicationMode::SYNC;
+    }
+  }
+  // TODO: C++23 std::unreachable()
+  return replication_coordination_glue::ReplicationMode::ASYNC;
+}
+
+class ReplQueryHandler {
  public:
-  explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler) : dbms_handler_(dbms_handler), handler_{*dbms_handler} {}
+  struct ReplicaInfo {
+    std::string name;
+    std::string socket_address;
+    ReplicationQuery::SyncMode sync_mode;
+    std::optional<double> timeout;
+    uint64_t current_timestamp_of_replica;
+    uint64_t current_number_of_timestamp_behind_master;
+    ReplicationQuery::ReplicaState state;
+  };
+
+  explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler) : handler_{*dbms_handler} {}
 
   /// @throw QueryRuntimeException if an error ocurred.
-  void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) override {
-    if (replication_role == ReplicationQuery::ReplicationRole::MAIN) {
-      if (!handler_.SetReplicationRoleMain()) {
-        throw QueryRuntimeException("Couldn't set role to main!");
-      }
-    } else {
-      if (!port || *port < 0 || *port > std::numeric_limits<uint16_t>::max()) {
+  void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) {
+    auto ValidatePort = [](std::optional<int64_t> port) -> void {
+      if (*port < 0 || *port > std::numeric_limits<uint16_t>::max()) {
         throw QueryRuntimeException("Port number invalid!");
       }
+    };
+    if (replication_role == ReplicationQuery::ReplicationRole::MAIN) {
+      if (!handler_.SetReplicationRoleMain()) {
+        throw QueryRuntimeException("Couldn't set replication role to main!");
+      }
+    } else {
+      ValidatePort(port);
 
       auto const config = memgraph::replication::ReplicationServerConfig{
           .ip_address = memgraph::replication::kDefaultReplicationServerIp,
@@ -299,11 +334,11 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
   }
 
   /// @throw QueryRuntimeException if an error ocurred.
-  ReplicationQuery::ReplicationRole ShowReplicationRole() const override {
+  ReplicationQuery::ReplicationRole ShowReplicationRole() const {
     switch (handler_.GetRole()) {
-      case memgraph::replication::ReplicationRole::MAIN:
+      case memgraph::replication_coordination_glue::ReplicationRole::MAIN:
         return ReplicationQuery::ReplicationRole::MAIN;
-      case memgraph::replication::ReplicationRole::REPLICA:
+      case memgraph::replication_coordination_glue::ReplicationRole::REPLICA:
         return ReplicationQuery::ReplicationRole::REPLICA;
     }
     throw QueryRuntimeException("Couldn't show replication role - invalid role set!");
@@ -311,36 +346,41 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
 
   /// @throw QueryRuntimeException if an error ocurred.
   void RegisterReplica(const std::string &name, const std::string &socket_address,
-                       const ReplicationQuery::SyncMode sync_mode,
-                       const std::chrono::seconds replica_check_frequency) override {
+                       const ReplicationQuery::SyncMode sync_mode, const std::chrono::seconds replica_check_frequency) {
+    // Coordinator is main by default so this check is OK although it should actually be nothing (neither main nor
+    // replica)
     if (handler_.IsReplica()) {
       // replica can't register another replica
       throw QueryRuntimeException("Replica can't register another replica!");
     }
 
-    auto repl_mode = convertToReplicationMode(sync_mode);
+    const auto repl_mode = convertToReplicationMode(sync_mode);
 
-    auto maybe_ip_and_port =
+    const auto maybe_ip_and_port =
         io::network::Endpoint::ParseSocketOrAddress(socket_address, memgraph::replication::kDefaultReplicationPort);
     if (maybe_ip_and_port) {
-      auto [ip, port] = *maybe_ip_and_port;
-      auto config = replication::ReplicationClientConfig{.name = name,
-                                                         .mode = repl_mode,
-                                                         .ip_address = ip,
-                                                         .port = port,
-                                                         .replica_check_frequency = replica_check_frequency,
-                                                         .ssl = std::nullopt};
-      auto ret = handler_.RegisterReplica(config);
-      if (ret.HasError()) {
+      const auto [ip, port] = *maybe_ip_and_port;
+      const auto replication_config =
+          replication::ReplicationClientConfig{.name = name,
+                                               .mode = repl_mode,
+                                               .ip_address = ip,
+                                               .port = port,
+                                               .replica_check_frequency = replica_check_frequency,
+                                               .ssl = std::nullopt};
+
+      const auto error = handler_.RegisterReplica(replication_config).HasError();
+
+      if (error) {
         throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name));
       }
+
     } else {
       throw QueryRuntimeException("Invalid socket address!");
     }
   }
 
   /// @throw QueryRuntimeException if an error occurred.
-  void DropReplica(std::string_view replica_name) override {
+  void DropReplica(std::string_view replica_name) {
     auto const result = handler_.UnregisterReplica(replica_name);
     switch (result) {
       using enum memgraph::dbms::UnregisterReplicaResult;
@@ -355,8 +395,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
     }
   }
 
-  using Replica = ReplicationQueryHandler::Replica;
-  std::vector<Replica> ShowReplicas() const override {
+  std::vector<ReplicaInfo> ShowReplicas(const dbms::Database &db) const {
     if (handler_.IsReplica()) {
       // replica can't show registered replicas (it shouldn't have any)
       throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!");
@@ -364,27 +403,19 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
 
     // TODO: Combine results? Have a single place with clients???
     //       Also authentication checks (replica + database visibility)
-    std::vector<storage::ReplicaInfo> repl_infos{};
-    dbms_handler_->ForOne([&repl_infos](dbms::Database *db) -> bool {
-      auto infos = db->storage()->ReplicasInfo();
-      if (!infos.empty()) {
-        repl_infos = std::move(infos);
-        return true;
-      }
-      return false;
-    });
-    std::vector<Replica> replicas;
+    const auto repl_infos = db.storage()->ReplicasInfo();
+    std::vector<ReplicaInfo> replicas;
     replicas.reserve(repl_infos.size());
 
-    const auto from_info = [](const auto &repl_info) -> Replica {
-      Replica replica;
+    const auto from_info = [](const auto &repl_info) -> ReplicaInfo {
+      ReplicaInfo replica;
       replica.name = repl_info.name;
       replica.socket_address = repl_info.endpoint.SocketAddress();
       switch (repl_info.mode) {
-        case memgraph::replication::ReplicationMode::SYNC:
+        case replication_coordination_glue::ReplicationMode::SYNC:
           replica.sync_mode = ReplicationQuery::SyncMode::SYNC;
           break;
-        case memgraph::replication::ReplicationMode::ASYNC:
+        case replication_coordination_glue::ReplicationMode::ASYNC:
           replica.sync_mode = ReplicationQuery::SyncMode::ASYNC;
           break;
       }
@@ -416,10 +447,104 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
   }
 
  private:
-  dbms::DbmsHandler *dbms_handler_;
   dbms::ReplicationHandler handler_;
 };
 
+class CoordQueryHandler final : public query::CoordinatorQueryHandler {
+ public:
+  explicit CoordQueryHandler(dbms::DbmsHandler *dbms_handler) : handler_ { *dbms_handler }
+#ifdef MG_ENTERPRISE
+  , coordinator_handler_(*dbms_handler)
+#endif
+  {
+  }
+
+#ifdef MG_ENTERPRISE
+  /// @throw QueryRuntimeException if an error ocurred.
+  void RegisterInstance(const std::string &coordinator_socket_address, const std::string &replication_socket_address,
+                        const std::chrono::seconds instance_check_frequency, const std::string &instance_name,
+                        CoordinatorQuery::SyncMode sync_mode) override {
+    const auto maybe_replication_ip_port =
+        io::network::Endpoint::ParseSocketOrAddress(replication_socket_address, std::nullopt);
+    if (!maybe_replication_ip_port) {
+      throw QueryRuntimeException("Invalid replication socket address!");
+    }
+
+    const auto maybe_coordinator_ip_port =
+        io::network::Endpoint::ParseSocketOrAddress(coordinator_socket_address, std::nullopt);
+    if (!maybe_replication_ip_port) {
+      throw QueryRuntimeException("Invalid replication socket address!");
+    }
+
+    const auto [replication_ip, replication_port] = *maybe_replication_ip_port;
+    const auto [coordinator_server_ip, coordinator_server_port] = *maybe_coordinator_ip_port;
+    const auto repl_config = coordination::CoordinatorClientConfig::ReplicationClientInfo{
+        .instance_name = instance_name,
+        .replication_mode = convertFromCoordinatorToReplicationMode(sync_mode),
+        .replication_ip_address = replication_ip,
+        .replication_port = replication_port};
+
+    auto coordinator_client_config =
+        coordination::CoordinatorClientConfig{.instance_name = instance_name,
+                                              .ip_address = coordinator_server_ip,
+                                              .port = coordinator_server_port,
+                                              .health_check_frequency_sec = instance_check_frequency,
+                                              .replication_client_info = repl_config,
+                                              .ssl = std::nullopt};
+
+    auto status = coordinator_handler_.RegisterInstance(coordinator_client_config);
+    switch (status) {
+      using enum memgraph::coordination::RegisterInstanceCoordinatorStatus;
+      case NAME_EXISTS:
+        throw QueryRuntimeException("Couldn't register replica instance since instance with such name already exists!");
+      case END_POINT_EXISTS:
+        throw QueryRuntimeException(
+            "Couldn't register replica instance since instance with such endpoint already exists!");
+      case COULD_NOT_BE_PERSISTED:
+        throw QueryRuntimeException("Couldn't register replica instance since it couldn't be persisted!");
+      case NOT_COORDINATOR:
+        throw QueryRuntimeException("Couldn't register replica instance since this instance is not a coordinator!");
+      case RPC_FAILED:
+        throw QueryRuntimeException(
+            "Couldn't register replica because promotion on replica failed! Check logs on replica to find out more "
+            "info!");
+      case SUCCESS:
+        break;
+    }
+  }
+
+  void SetInstanceToMain(const std::string &instance_name) override {
+    auto status = coordinator_handler_.SetInstanceToMain(instance_name);
+    switch (status) {
+      using enum memgraph::coordination::SetInstanceToMainCoordinatorStatus;
+      case NO_INSTANCE_WITH_NAME:
+        throw QueryRuntimeException("No instance with such name!");
+      case NOT_COORDINATOR:
+        throw QueryRuntimeException("Couldn't set replica instance to main since this instance is not a coordinator!");
+      case COULD_NOT_PROMOTE_TO_MAIN:
+        throw QueryRuntimeException(
+            "Couldn't set replica instance to main. Check coordinator and replica for more logs");
+      case SUCCESS:
+        break;
+    }
+  }
+
+#endif
+
+#ifdef MG_ENTERPRISE
+  std::vector<coordination::CoordinatorInstanceStatus> ShowInstances() const override {
+    return coordinator_handler_.ShowInstances();
+  }
+
+#endif
+
+ private:
+  dbms::ReplicationHandler handler_;
+#ifdef MG_ENTERPRISE
+  dbms::CoordinatorHandler coordinator_handler_;
+#endif
+};
+
 /// returns false if the replication role can't be set
 /// @throw QueryRuntimeException if an error ocurred.
 
@@ -711,8 +836,8 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
 }  // namespace
 
 Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters,
-                                dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config,
-                                std::vector<Notification> *notifications) {
+                                dbms::DbmsHandler *dbms_handler, CurrentDB &current_db,
+                                const query::InterpreterConfig &config, std::vector<Notification> *notifications) {
   // TODO: MemoryResource for EvaluationContext, it should probably be passed as
   // the argument to Callback.
   EvaluationContext evaluation_context;
@@ -723,6 +848,15 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
   Callback callback;
   switch (repl_query->action_) {
     case ReplicationQuery::Action::SET_REPLICATION_ROLE: {
+#ifdef MG_ENTERPRISE
+      if (FLAGS_coordinator) {
+        throw QueryRuntimeException("Coordinator can't set roles!");
+      }
+      if (FLAGS_coordinator_server_port) {
+        throw QueryRuntimeException("Can't set role manually on instance with coordinator server port.");
+      }
+#endif
+
       auto port = EvaluateOptionalExpression(repl_query->port_, evaluator);
       std::optional<int64_t> maybe_port;
       if (port.IsInt()) {
@@ -743,6 +877,12 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
       return callback;
     }
     case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: {
+#ifdef MG_ENTERPRISE
+      if (FLAGS_coordinator) {
+        throw QueryRuntimeException("Coordinator doesn't have a replication role!");
+      }
+#endif
+
       callback.header = {"replication role"};
       callback.fn = [handler = ReplQueryHandler{dbms_handler}] {
         auto mode = handler.ShowReplicationRole();
@@ -758,7 +898,12 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
       return callback;
     }
     case ReplicationQuery::Action::REGISTER_REPLICA: {
-      const auto &name = repl_query->replica_name_;
+#ifdef MG_ENTERPRISE
+      if (FLAGS_coordinator_server_port) {
+        throw QueryRuntimeException("Can't register replica manually on instance with coordinator server port.");
+      }
+#endif
+      const auto &name = repl_query->instance_name_;
       const auto &sync_mode = repl_query->sync_mode_;
       auto socket_address = repl_query->socket_address_->Accept(evaluator);
       const auto replica_check_frequency = config.replication_replica_check_frequency;
@@ -769,25 +914,38 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
         return std::vector<std::vector<TypedValue>>();
       };
       notifications->emplace_back(SeverityLevel::INFO, NotificationCode::REGISTER_REPLICA,
-                                  fmt::format("Replica {} is registered.", repl_query->replica_name_));
+                                  fmt::format("Replica {} is registered.", repl_query->instance_name_));
       return callback;
     }
+
     case ReplicationQuery::Action::DROP_REPLICA: {
-      const auto &name = repl_query->replica_name_;
+#ifdef MG_ENTERPRISE
+      if (FLAGS_coordinator_server_port) {
+        throw QueryRuntimeException("Can't drop replica manually on instance with coordinator server port.");
+      }
+#endif
+      const auto &name = repl_query->instance_name_;
       callback.fn = [handler = ReplQueryHandler{dbms_handler}, name]() mutable {
         handler.DropReplica(name);
         return std::vector<std::vector<TypedValue>>();
       };
       notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_REPLICA,
-                                  fmt::format("Replica {} is dropped.", repl_query->replica_name_));
+                                  fmt::format("Replica {} is dropped.", repl_query->instance_name_));
       return callback;
     }
     case ReplicationQuery::Action::SHOW_REPLICAS: {
+#ifdef MG_ENTERPRISE
+      if (FLAGS_coordinator) {
+        throw QueryRuntimeException("Coordinator cannot call SHOW REPLICAS! Use SHOW REPLICATION CLUSTER instead.");
+      }
+#endif
+
       callback.header = {
           "name", "socket_address", "sync_mode", "current_timestamp_of_replica", "number_of_timestamp_behind_master",
           "state"};
-      callback.fn = [handler = ReplQueryHandler{dbms_handler}, replica_nfields = callback.header.size()] {
-        const auto &replicas = handler.ShowReplicas();
+      callback.fn = [handler = ReplQueryHandler{dbms_handler}, replica_nfields = callback.header.size(),
+                     db_acc = current_db.db_acc_] {
+        const auto &replicas = handler.ShowReplicas(*db_acc->get());
         auto typed_replicas = std::vector<std::vector<TypedValue>>{};
         typed_replicas.reserve(replicas.size());
         for (const auto &replica : replicas) {
@@ -833,6 +991,110 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
   }
 }
 
+Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Parameters &parameters,
+                                dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config,
+                                std::vector<Notification> *notifications) {
+  Callback callback;
+  switch (coordinator_query->action_) {
+    case CoordinatorQuery::Action::REGISTER_INSTANCE: {
+      if (!license::global_license_checker.IsEnterpriseValidFast()) {
+        throw QueryException("Trying to use enterprise feature without a valid license.");
+      }
+#ifdef MG_ENTERPRISE
+      if constexpr (!coordination::allow_ha) {
+        throw QueryRuntimeException(
+            "High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
+            "be able to use this functionality.");
+      }
+      if (!FLAGS_coordinator) {
+        throw QueryRuntimeException("Only coordinator can register coordinator server!");
+      }
+      // TODO: MemoryResource for EvaluationContext, it should probably be passed as
+      // the argument to Callback.
+      EvaluationContext evaluation_context{.timestamp = QueryTimestamp(), .parameters = parameters};
+      auto evaluator = PrimitiveLiteralExpressionEvaluator{evaluation_context};
+
+      auto coordinator_socket_address_tv = coordinator_query->coordinator_socket_address_->Accept(evaluator);
+      auto replication_socket_address_tv = coordinator_query->replication_socket_address_->Accept(evaluator);
+      callback.fn = [handler = CoordQueryHandler{dbms_handler}, coordinator_socket_address_tv,
+                     replication_socket_address_tv, main_check_frequency = config.replication_replica_check_frequency,
+                     instance_name = coordinator_query->instance_name_,
+                     sync_mode = coordinator_query->sync_mode_]() mutable {
+        handler.RegisterInstance(std::string(coordinator_socket_address_tv.ValueString()),
+                                 std::string(replication_socket_address_tv.ValueString()), main_check_frequency,
+                                 instance_name, sync_mode);
+        return std::vector<std::vector<TypedValue>>();
+      };
+
+      notifications->emplace_back(
+          SeverityLevel::INFO, NotificationCode::REGISTER_COORDINATOR_SERVER,
+          fmt::format("Coordinator has registered coordinator server on {} for instance {}.",
+                      coordinator_socket_address_tv.ValueString(), coordinator_query->instance_name_));
+      return callback;
+#endif
+    }
+    case CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN: {
+      if (!license::global_license_checker.IsEnterpriseValidFast()) {
+        throw QueryException("Trying to use enterprise feature without a valid license.");
+      }
+#ifdef MG_ENTERPRISE
+      if constexpr (!coordination::allow_ha) {
+        throw QueryRuntimeException(
+            "High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
+            "be able to use this functionality.");
+      }
+      if (!FLAGS_coordinator) {
+        throw QueryRuntimeException("Only coordinator can register coordinator server!");
+      }
+      // TODO: MemoryResource for EvaluationContext, it should probably be passed as
+      // the argument to Callback.
+      EvaluationContext evaluation_context{.timestamp = QueryTimestamp(), .parameters = parameters};
+      auto evaluator = PrimitiveLiteralExpressionEvaluator{evaluation_context};
+
+      callback.fn = [handler = CoordQueryHandler{dbms_handler},
+                     instance_name = coordinator_query->instance_name_]() mutable {
+        handler.SetInstanceToMain(instance_name);
+        return std::vector<std::vector<TypedValue>>();
+      };
+
+      return callback;
+#endif
+    }
+    case CoordinatorQuery::Action::SHOW_REPLICATION_CLUSTER: {
+      if (!license::global_license_checker.IsEnterpriseValidFast()) {
+        throw QueryException("Trying to use enterprise feature without a valid license.");
+      }
+#ifdef MG_ENTERPRISE
+      if constexpr (!coordination::allow_ha) {
+        throw QueryRuntimeException(
+            "High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
+            "be able to use this functionality.");
+      }
+      if (!FLAGS_coordinator) {
+        throw QueryRuntimeException("Only coordinator can run SHOW REPLICATION CLUSTER.");
+      }
+
+      callback.header = {"name", "socket_address", "alive", "role"};
+      callback.fn = [handler = CoordQueryHandler{dbms_handler}, replica_nfields = callback.header.size()]() mutable {
+        auto const instances = handler.ShowInstances();
+        std::vector<std::vector<TypedValue>> result{};
+        result.reserve(result.size());
+
+        std::ranges::transform(instances, std::back_inserter(result),
+                               [](const auto &status) -> std::vector<TypedValue> {
+                                 return {TypedValue{status.instance_name}, TypedValue{status.socket_address},
+                                         TypedValue{status.is_alive}, TypedValue{status.replication_role}};
+                               });
+
+        return result;
+      };
+      return callback;
+#endif
+    }
+      return callback;
+  }
+}
+
 stream::CommonStreamInfo GetCommonStreamInfo(StreamQuery *stream_query, ExpressionVisitor<TypedValue> &evaluator) {
   return {
       .batch_interval = GetOptionalValue<std::chrono::milliseconds>(stream_query->batch_interval_, evaluator)
@@ -963,12 +1225,19 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters &paramete
           throw utils::BasicException("Parameter BATCH_LIMIT cannot hold negative value");
         }
 
-        callback.fn = [streams = db_acc->streams(), stream_name = stream_query->stream_name_, batch_limit, timeout]() {
+        callback.fn = [db_acc, streams = db_acc->streams(), stream_name = stream_query->stream_name_, batch_limit,
+                       timeout]() {
+          if (db_acc.is_deleting()) {
+            throw QueryException("Can not start stream while database is being dropped.");
+          }
           streams->StartWithLimit(stream_name, static_cast<uint64_t>(batch_limit.value()), timeout);
           return std::vector<std::vector<TypedValue>>{};
         };
       } else {
-        callback.fn = [streams = db_acc->streams(), stream_name = stream_query->stream_name_]() {
+        callback.fn = [db_acc, streams = db_acc->streams(), stream_name = stream_query->stream_name_]() {
+          if (db_acc.is_deleting()) {
+            throw QueryException("Can not start stream while database is being dropped.");
+          }
           streams->Start(stream_name);
           return std::vector<std::vector<TypedValue>>{};
         };
@@ -1457,8 +1726,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper,
   std::function<void()> handler;
 
   if (query_upper == "BEGIN") {
-    query_executions_.clear();
-    transaction_queries_->clear();
+    ResetInterpreter();
     // TODO: Evaluate doing move(extras). Currently the extras is very small, but this will be important if it ever
     // becomes large.
     handler = [this, extras = extras] {
@@ -1818,7 +2086,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
 PreparedQuery PrepareDumpQuery(ParsedQuery parsed_query, CurrentDB &current_db) {
   MG_ASSERT(current_db.execution_db_accessor_, "Dump query expects a current DB transaction");
   auto *dba = &*current_db.execution_db_accessor_;
-  auto plan = std::make_shared<PullPlanDump>(dba);
+  auto plan = std::make_shared<PullPlanDump>(dba, *current_db.db_acc_);
   return PreparedQuery{
       {"QUERY"},
       std::move(parsed_query.required_privileges),
@@ -2260,14 +2528,14 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
 
 PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
                                       std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler,
-                                      const InterpreterConfig &config) {
+                                      CurrentDB &current_db, const InterpreterConfig &config) {
   if (in_explicit_transaction) {
     throw ReplicationModificationInMulticommandTxException();
   }
 
   auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query);
-  auto callback =
-      HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, config, notifications);
+  auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, current_db, config,
+                                         notifications);
 
   return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
                        [callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
@@ -2286,6 +2554,34 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit
   // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
 }
 
+PreparedQuery PrepareCoordinatorQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
+                                      std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler,
+                                      const InterpreterConfig &config) {
+  if (in_explicit_transaction) {
+    throw CoordinatorModificationInMulticommandTxException();
+  }
+
+  auto *coordinator_query = utils::Downcast<CoordinatorQuery>(parsed_query.query);
+  auto callback =
+      HandleCoordinatorQuery(coordinator_query, parsed_query.parameters, &dbms_handler, config, notifications);
+
+  return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
+                       [callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
+                           AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
+                         if (UNLIKELY(!pull_plan)) {
+                           pull_plan = std::make_shared<PullPlanVector>(callback_fn());
+                         }
+
+                         if (pull_plan->Pull(stream, n)) [[likely]] {
+                           return QueryHandlerResult::COMMIT;
+                         }
+                         return std::nullopt;
+                       },
+                       RWType::NONE};
+  // False positive report for the std::make_shared above
+  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+}
+
 PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_transaction, CurrentDB &current_db) {
   if (in_explicit_transaction) {
     throw LockPathModificationInMulticommandTxException();
@@ -2786,8 +3082,8 @@ PreparedQuery PrepareEdgeImportModeQuery(ParsedQuery parsed_query, CurrentDB &cu
                        RWType::NONE};
 }
 
-PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
-                                         CurrentDB &current_db) {
+PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_explicit_transaction, CurrentDB &current_db,
+                                         replication_coordination_glue::ReplicationRole replication_role) {
   if (in_explicit_transaction) {
     throw CreateSnapshotInMulticommandTxException();
   }
@@ -2802,9 +3098,10 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli
   return PreparedQuery{
       {},
       std::move(parsed_query.required_privileges),
-      [storage](AnyStream * /*stream*/, std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> {
+      [storage, replication_role](AnyStream * /*stream*/,
+                                  std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> {
         auto *mem_storage = static_cast<storage::InMemoryStorage *>(storage);
-        if (auto maybe_error = mem_storage->CreateSnapshot(); maybe_error.HasError()) {
+        if (auto maybe_error = mem_storage->CreateSnapshot(replication_role); maybe_error.HasError()) {
           switch (maybe_error.GetError()) {
             case storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica:
               throw utils::BasicException(
@@ -2864,7 +3161,7 @@ auto ShowTransactions(const std::unordered_set<Interpreter *> &interpreters, con
 
     auto get_interpreter_db_name = [&]() -> std::string const & {
       static std::string all;
-      return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->id() : all;
+      return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->name() : all;
     };
     if (transaction_id.has_value() &&
         (interpreter->username_ == username || privilege_checker(get_interpreter_db_name()))) {
@@ -2977,15 +3274,16 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici
   auto *info_query = utils::Downcast<DatabaseInfoQuery>(parsed_query.query);
   std::vector<std::string> header;
   std::function<std::pair<std::vector<std::vector<TypedValue>>, QueryHandlerResult>()> handler;
-
+  auto *database = current_db.db_acc_->get();
   switch (info_query->info_type_) {
     case DatabaseInfoQuery::InfoType::INDEX: {
       header = {"index type", "label", "property", "count"};
-      handler = [storage = current_db.db_acc_->get()->storage(), dba] {
+      handler = [database, dba] {
+        auto *storage = database->storage();
         const std::string_view label_index_mark{"label"};
         const std::string_view label_property_index_mark{"label+property"};
         auto info = dba->ListAllIndices();
-        auto storage_acc = storage->Access();
+        auto storage_acc = database->Access();
         std::vector<std::vector<TypedValue>> results;
         results.reserve(info.label.size() + info.label_property.size());
         for (const auto &item : info.label) {
@@ -3045,7 +3343,7 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici
     case DatabaseInfoQuery::InfoType::EDGE_TYPES: {
       header = {"edge types"};
       handler = [storage = current_db.db_acc_->get()->storage(), dba] {
-        if (!storage->config_.items.enable_schema_metadata) {
+        if (!storage->config_.salient.items.enable_schema_metadata) {
           throw QueryRuntimeException(
               "The metadata collection for edge-types is disabled. To enable it, restart your instance and set the "
               "storage-enable-schema-metadata flag to True.");
@@ -3065,7 +3363,7 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici
     case DatabaseInfoQuery::InfoType::NODE_LABELS: {
       header = {"node labels"};
       handler = [storage = current_db.db_acc_->get()->storage(), dba] {
-        if (!storage->config_.items.enable_schema_metadata) {
+        if (!storage->config_.salient.items.enable_schema_metadata) {
           throw QueryRuntimeException(
               "The metadata collection for node-labels is disabled. To enable it, restart your instance and set the "
               "storage-enable-schema-metadata flag to True.");
@@ -3124,7 +3422,7 @@ PreparedQuery PrepareSystemInfoQuery(ParsedQuery parsed_query, bool in_explicit_
         const int64_t vm_max_map_count_storage_info =
             vm_max_map_count.has_value() ? vm_max_map_count.value() : memgraph::utils::VM_MAX_MAP_COUNT_DEFAULT;
         std::vector<std::vector<TypedValue>> results{
-            {TypedValue("name"), TypedValue(storage->id())},
+            {TypedValue("name"), TypedValue(storage->name())},
             {TypedValue("vertex_count"), TypedValue(static_cast<int64_t>(info.vertex_count))},
             {TypedValue("edge_count"), TypedValue(static_cast<int64_t>(info.edge_count))},
             {TypedValue("average_degree"), TypedValue(info.average_degree)},
@@ -3390,8 +3688,6 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
   if (!license::global_license_checker.IsEnterpriseValidFast()) {
     throw QueryException("Trying to use enterprise feature without a valid license.");
   }
-  // TODO: Remove once replicas support multi-tenant replication
-  if (!current_db.db_acc_) throw DatabaseContextRequiredException("Multi database queries require a defined database.");
 
   auto *query = utils::Downcast<MultiDatabaseQuery>(parsed_query.query);
   auto *db_handler = interpreter_context->dbms_handler;
@@ -3399,7 +3695,7 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
   const bool is_replica = interpreter_context->repl_state->IsReplica();
 
   switch (query->action_) {
-    case MultiDatabaseQuery::Action::CREATE:
+    case MultiDatabaseQuery::Action::CREATE: {
       if (is_replica) {
         throw QueryException("Query forbidden on the replica!");
       }
@@ -3440,8 +3736,8 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
           RWType::W,
           ""  // No target DB possible
       };
-
-    case MultiDatabaseQuery::Action::USE:
+    }
+    case MultiDatabaseQuery::Action::USE: {
       if (current_db.in_explicit_db_) {
         throw QueryException("Database switching is prohibited if session explicitly defines the used database");
       }
@@ -3456,7 +3752,7 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
                              std::string res;
 
                              try {
-                               if (current_db.db_acc_ && db_name == current_db.db_acc_->get()->id()) {
+                               if (current_db.db_acc_ && db_name == current_db.db_acc_->get()->name()) {
                                  res = "Already using " + db_name;
                                } else {
                                  auto tmp = db_handler->Get(db_name);
@@ -3477,11 +3773,12 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
                            },
                            RWType::NONE,
                            query->db_name_};
-
-    case MultiDatabaseQuery::Action::DROP:
+    }
+    case MultiDatabaseQuery::Action::DROP: {
       if (is_replica) {
         throw QueryException("Query forbidden on the replica!");
       }
+
       return PreparedQuery{
           {"STATUS"},
           std::move(parsed_query.required_privileges),
@@ -3491,10 +3788,10 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
 
             try {
               // Remove database
-              auto success = db_handler->Delete(db_name);
+              auto success = db_handler->TryDelete(db_name);
               if (!success.HasError()) {
                 // Remove from auth
-                auth->DeleteDatabase(db_name);
+                if (auth) auth->DeleteDatabase(db_name);
               } else {
                 switch (success.GetError()) {
                   case dbms::DeleteError::DEFAULT_DB:
@@ -3522,48 +3819,56 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
           },
           RWType::W,
           query->db_name_};
-  }
+    }
+    case MultiDatabaseQuery::Action::SHOW: {
+      return PreparedQuery{
+          {"Current"},
+          std::move(parsed_query.required_privileges),
+          [db_acc = current_db.db_acc_, pull_plan = std::shared_ptr<PullPlanVector>(nullptr)](
+              AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
+            if (!pull_plan) {
+              std::vector<std::vector<TypedValue>> results;
+              auto db_name = db_acc ? TypedValue{db_acc->get()->storage()->name()} : TypedValue{};
+              results.push_back({std::move(db_name)});
+              pull_plan = std::make_shared<PullPlanVector>(std::move(results));
+            }
+
+            if (pull_plan->Pull(stream, n)) {
+              return QueryHandlerResult::NOTHING;
+            }
+            return std::nullopt;
+          },
+          RWType::NONE,
+          ""  // No target DB
+      };
+    }
+  };
 #else
   throw QueryException("Query not supported.");
 #endif
 }
 
-PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, CurrentDB &current_db,
-                                        InterpreterContext *interpreter_context,
+PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, InterpreterContext *interpreter_context,
                                         const std::optional<std::string> &username) {
 #ifdef MG_ENTERPRISE
-
-  // TODO: split query into two, Databases (no need for current_db), & Current database (uses current_db)
-  MG_ASSERT(current_db.db_acc_, "Show Database Level query expects a current DB");
-  storage::Storage *storage = current_db.db_acc_->get()->storage();
-
   if (!license::global_license_checker.IsEnterpriseValidFast()) {
     throw QueryException("Trying to use enterprise feature without a valid license.");
   }
 
-  // TODO pick directly from ic
   auto *db_handler = interpreter_context->dbms_handler;
   AuthQueryHandler *auth = interpreter_context->auth;
 
   Callback callback;
-  callback.header = {"Name", "Current"};
-  callback.fn = [auth, storage, db_handler, username]() mutable -> std::vector<std::vector<TypedValue>> {
+  callback.header = {"Name"};
+  callback.fn = [auth, db_handler, username]() mutable -> std::vector<std::vector<TypedValue>> {
     std::vector<std::vector<TypedValue>> status;
-    const auto &in_use = storage->id();
-    bool found_current = false;
-
     auto gen_status = [&]<typename T, typename K>(T all, K denied) {
       Sort(all);
       Sort(denied);
 
       status.reserve(all.size());
       for (const auto &name : all) {
-        TypedValue use("");
-        if (!found_current && Same(name, in_use)) {
-          use = TypedValue("*");
-          found_current = true;
-        }
-        status.push_back({TypedValue(name), std::move(use)});
+        status.push_back({TypedValue(name)});
       }
 
       // No denied databases (no need to filter them out)
@@ -3593,7 +3898,6 @@ PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, CurrentDB &cur
       }
     }
 
-    if (!found_current) throw QueryRuntimeException("Missing current database!");
     return status;
   };
 
@@ -3629,15 +3933,13 @@ void Interpreter::BeginTransaction(QueryExtras const &extras) {
 void Interpreter::CommitTransaction() {
   const auto prepared_query = PrepareTransactionQuery("COMMIT");
   prepared_query.query_handler(nullptr, {});
-  query_executions_.clear();
-  transaction_queries_->clear();
+  ResetInterpreter();
 }
 
 void Interpreter::RollbackTransaction() {
   const auto prepared_query = PrepareTransactionQuery("ROLLBACK");
   prepared_query.query_handler(nullptr, {});
-  query_executions_.clear();
-  transaction_queries_->clear();
+  ResetInterpreter();
 }
 
 #if MG_ENTERPRISE
@@ -3653,11 +3955,6 @@ void Interpreter::SetCurrentDB(std::string_view db_name, bool in_explicit_db) {
 Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
                                                 const std::map<std::string, storage::PropertyValue> &params,
                                                 QueryExtras const &extras) {
-  // TODO: Remove once the interpreter is storage/tx independent and could run without an associated database
-  if (!current_db_.db_acc_) {
-    throw DatabaseContextRequiredException("Database required for the query.");
-  }
-
   // Handle transaction control queries.
   const auto upper_case_query = utils::ToUpperCase(query_string);
   const auto trimmed_query = utils::Trim(upper_case_query);
@@ -3671,18 +3968,16 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
     return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid, {}};
   }
 
-  if (!in_explicit_transaction_) {
-    transaction_queries_->clear();
-  }
-  // Don't save BEGIN, COMMIT or ROLLBACK
-  transaction_queries_->push_back(query_string);
+  // NOTE: query_string is not BEGIN, COMMIT or ROLLBACK
 
   // All queries other than transaction control queries advance the command in
   // an explicit transaction block.
   if (in_explicit_transaction_) {
+    transaction_queries_->push_back(query_string);
     AdvanceCommand();
   } else {
-    query_executions_.clear();
+    ResetInterpreter();
+    transaction_queries_->push_back(query_string);
     if (current_db_.db_transactional_accessor_ /* && !in_explicit_transaction_*/) {
       // If we're not in an explicit transaction block and we have an open
       // transaction, abort it since we're about to prepare a new query.
@@ -3740,6 +4035,37 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
     // field with an improved estimate.
     query_execution->summary["cost_estimate"] = 0.0;
 
+    // System queries require strict ordering; since there is no MVCC-like thing, we allow single queries
+    bool system_queries = utils::Downcast<AuthQuery>(parsed_query.query) ||
+                          utils::Downcast<MultiDatabaseQuery>(parsed_query.query) ||
+                          utils::Downcast<ShowDatabasesQuery>(parsed_query.query) ||
+                          utils::Downcast<ReplicationQuery>(parsed_query.query);
+
+    // TODO Split SHOW REPLICAS (which needs the db) and other replication queries
+    auto system_transaction_guard = std::invoke([&]() -> std::optional<SystemTransactionGuard> {
+      if (system_queries) {
+        // TODO: Ordering between system and data queries
+        // Start a system transaction
+        auto system_unique = std::unique_lock{interpreter_context_->dbms_handler->system_lock_, std::defer_lock};
+        if (!system_unique.try_lock_for(std::chrono::milliseconds(kSystemTxTryMS))) {
+          throw ConcurrentSystemQueriesException("Multiple concurrent system queries are not supported.");
+        }
+        return std::optional<SystemTransactionGuard>{std::in_place, std::move(system_unique),
+                                                     *interpreter_context_->dbms_handler};
+      }
+      return std::nullopt;
+    });
+
+    // Some queries do not require a database to be executed (current_db_ won't be passed on to the Prepare*; special
+    // case for use database which overwrites the current database)
+    bool no_db_required = system_queries || utils::Downcast<ShowConfigQuery>(parsed_query.query) ||
+                          utils::Downcast<SettingQuery>(parsed_query.query) ||
+                          utils::Downcast<VersionQuery>(parsed_query.query) ||
+                          utils::Downcast<TransactionQueueQuery>(parsed_query.query);
+    if (!no_db_required && !current_db_.db_acc_) {
+      throw DatabaseContextRequiredException("Database required for the query.");
+    }
+
     // Some queries require an active transaction in order to be prepared.
     // TODO: make a better analysis visitor over the `parsed_query.query`
     bool requires_db_transaction =
@@ -3758,10 +4084,12 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
       SetupDatabaseTransaction(could_commit, unique);
     }
 
-    // TODO: none database transaction (assuming mutually exclusive from DB transactions)
-    // if (!requires_db_transaction) {
-    //   /* something */
-    // }
+#ifdef MG_ENTERPRISE
+    if (FLAGS_coordinator && !utils::Downcast<CoordinatorQuery>(parsed_query.query) &&
+        !utils::Downcast<SettingQuery>(parsed_query.query)) {
+      throw QueryRuntimeException("Coordinator can run only coordinator queries!");
+    }
+#endif
 
     utils::Timer planning_timer;
     PreparedQuery prepared_query;
@@ -3804,6 +4132,10 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
       /// TODO: make replication DB agnostic
       prepared_query =
           PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
+                                  *interpreter_context_->dbms_handler, current_db_, interpreter_context_->config);
+    } else if (utils::Downcast<CoordinatorQuery>(parsed_query.query)) {
+      prepared_query =
+          PrepareCoordinatorQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
                                   *interpreter_context_->dbms_handler, interpreter_context_->config);
     } else if (utils::Downcast<LockPathQuery>(parsed_query.query)) {
       prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
@@ -3823,7 +4155,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
     } else if (utils::Downcast<IsolationLevelQuery>(parsed_query.query)) {
       prepared_query = PrepareIsolationLevelQuery(std::move(parsed_query), in_explicit_transaction_, current_db_, this);
     } else if (utils::Downcast<CreateSnapshotQuery>(parsed_query.query)) {
-      prepared_query = PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
+      auto const replication_role = interpreter_context_->repl_state->GetRole();
+      prepared_query =
+          PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, current_db_, replication_role);
     } else if (utils::Downcast<SettingQuery>(parsed_query.query)) {
       /// SYSTEM PURE
       prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_);
@@ -3844,12 +4178,11 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
         throw MultiDatabaseQueryInMulticommandTxException();
       }
       /// SYSTEM (Replication) + INTERPRETER
-      prepared_query =
-          PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_);
+      // DMG_ASSERT(system_guard);
+      prepared_query = PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_
+                                                 /*, *system_guard*/);
     } else if (utils::Downcast<ShowDatabasesQuery>(parsed_query.query)) {
-      /// SYSTEM PURE ("SHOW DATABASES")
-      /// INTERPRETER (TODO: "SHOW DATABASE")
-      prepared_query = PrepareShowDatabasesQuery(std::move(parsed_query), current_db_, interpreter_context_, username_);
+      prepared_query = PrepareShowDatabasesQuery(std::move(parsed_query), interpreter_context_, username_);
     } else if (utils::Downcast<EdgeImportModeQuery>(parsed_query.query)) {
       if (in_explicit_transaction_) {
         throw EdgeImportModeModificationInMulticommandTxException();
@@ -3874,10 +4207,12 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
 
     // Set the target db to the current db (some queries have different target from the current db)
     if (!query_execution->prepared_query->db) {
-      query_execution->prepared_query->db = current_db_.db_acc_->get()->id();
+      query_execution->prepared_query->db = current_db_.db_acc_->get()->name();
     }
     query_execution->summary["db"] = *query_execution->prepared_query->db;
 
+    // prepare is done, move system txn guard to be owned by interpreter
+    system_transaction_guard_ = std::move(system_transaction_guard);
     return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid,
             query_execution->prepared_query->db};
   } catch (const utils::BasicException &) {
@@ -3889,9 +4224,11 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
     throw;
   }
 }
+
 void Interpreter::SetupDatabaseTransaction(bool couldCommit, bool unique) {
   current_db_.SetupDatabaseTransaction(GetIsolationLevelOverride(), couldCommit, unique);
 }
+
 void Interpreter::SetupInterpreterTransaction(const QueryExtras &extras) {
   metrics::IncrementCounter(metrics::ActiveTransactions);
   transaction_status_.store(TransactionStatus::ACTIVE, std::memory_order_release);
@@ -3970,7 +4307,9 @@ void RunTriggersAfterCommit(dbms::DatabaseAccess db_acc, InterpreterContext *int
       continue;
     }
 
-    auto maybe_commit_error = db_accessor.Commit();
+    bool is_main = interpreter_context->repl_state->IsMain();
+    auto maybe_commit_error = db_accessor.Commit({.is_main = is_main}, db_acc);
+
     if (maybe_commit_error.HasError()) {
       const auto &error = maybe_commit_error.GetError();
 
@@ -4021,10 +4360,35 @@ void Interpreter::Commit() {
   // We should document clearly that all results should be pulled to complete
   // a query.
   current_transaction_.reset();
-  if (!current_db_.db_transactional_accessor_) return;
+  if (!current_db_.db_transactional_accessor_ || !current_db_.db_acc_) {
+    // No database nor db transaction; check for system transaction
+    if (!system_transaction_guard_) return;
 
-  // TODO: Better (or removed) check
-  if (!current_db_.db_acc_) return;
+    // TODO Distinguish between data and system transaction state
+    // Think about updating the status to a struct with bitfield
+    // Clean transaction status on exit
+    utils::OnScopeExit clean_status([this]() {
+      system_transaction_guard_.reset();
+      // System transactions are not terminable
+      // Durability has happened at time of PULL
+      // Commit is doing replication and timestamp update
+      // The DBMS does not support MVCC, so doing durability here doesn't change the overall logic; we cannot abort!
+      // What we are trying to do is set the transaction back to IDLE
+      // We cannot simply put it to IDLE, since the status is used as a syncronization method and we have to follow
+      // its logic. There are 2 states when we could update to IDLE (ACTIVE and TERMINATED).
+      auto expected = TransactionStatus::ACTIVE;
+      while (!transaction_status_.compare_exchange_weak(expected, TransactionStatus::IDLE)) {
+        if (expected == TransactionStatus::TERMINATED) {
+          continue;
+        }
+        expected = TransactionStatus::ACTIVE;
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+      }
+    });
+
+    system_transaction_guard_->Commit();
+    return;
+  }
   auto *db = current_db_.db_acc_->get();
 
   /*
@@ -4094,19 +4458,19 @@ void Interpreter::Commit() {
   };
   utils::OnScopeExit members_reseter(reset_necessary_members);
 
-  auto commit_confirmed_by_all_sync_repplicas = true;
+  auto commit_confirmed_by_all_sync_replicas = true;
 
-  auto maybe_commit_error =
-      current_db_.db_transactional_accessor_->Commit(std::nullopt, interpreter_context_->repl_state->IsMain());
+  bool is_main = interpreter_context_->repl_state->IsMain();
+  auto maybe_commit_error = current_db_.db_transactional_accessor_->Commit({.is_main = is_main}, current_db_.db_acc_);
   if (maybe_commit_error.HasError()) {
     const auto &error = maybe_commit_error.GetError();
 
     std::visit(
         [&execution_db_accessor = current_db_.execution_db_accessor_,
-         &commit_confirmed_by_all_sync_repplicas]<typename T>(T &&arg) {
+         &commit_confirmed_by_all_sync_replicas]<typename T>(const T &arg) {
           using ErrorType = std::remove_cvref_t<T>;
           if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
-            commit_confirmed_by_all_sync_repplicas = false;
+            commit_confirmed_by_all_sync_replicas = false;
           } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) {
             const auto &constraint_violation = arg;
             auto &label_name = execution_db_accessor->LabelToName(constraint_violation.label);
@@ -4146,7 +4510,6 @@ void Interpreter::Commit() {
   if (trigger_context && db->trigger_store()->AfterCommitTriggers().size() > 0) {
     db->AddTask([this, trigger_context = std::move(*trigger_context),
                  user_transaction = std::shared_ptr(std::move(current_db_.db_transactional_accessor_))]() mutable {
-      // TODO: Should this take the db_ and not Access()?
       RunTriggersAfterCommit(*current_db_.db_acc_, interpreter_context_, std::move(trigger_context),
                              &this->transaction_status_);
       user_transaction->FinalizeTransaction();
@@ -4155,7 +4518,7 @@ void Interpreter::Commit() {
   }
 
   SPDLOG_DEBUG("Finished committing the transaction");
-  if (!commit_confirmed_by_all_sync_repplicas) {
+  if (!commit_confirmed_by_all_sync_replicas) {
     throw ReplicationException("At least one SYNC replica has not confirmed committing last transaction.");
   }
 }
diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp
index 5cb73cb07..42100059c 100644
--- a/src/query/interpreter.hpp
+++ b/src/query/interpreter.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,6 +16,7 @@
 #include <gflags/gflags.h>
 
 #include "dbms/database.hpp"
+#include "dbms/dbms_handler.hpp"
 #include "memory/query_memory_control.hpp"
 #include "query/auth_checker.hpp"
 #include "query/auth_query_handler.hpp"
@@ -51,6 +52,10 @@
 #include "utils/timer.hpp"
 #include "utils/tsc.hpp"
 
+#ifdef MG_ENTERPRISE
+#include "coordination/coordinator_instance_status.hpp"
+#endif
+
 namespace memgraph::metrics {
 extern const Event FailedQuery;
 extern const Event FailedPrepare;
@@ -67,16 +72,16 @@ inline constexpr size_t kExecutionPoolMaxBlockSize = 1024UL;  // 2 ^ 10
 
 enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
 
-class ReplicationQueryHandler {
+class CoordinatorQueryHandler {
  public:
-  ReplicationQueryHandler() = default;
-  virtual ~ReplicationQueryHandler() = default;
+  CoordinatorQueryHandler() = default;
+  virtual ~CoordinatorQueryHandler() = default;
 
-  ReplicationQueryHandler(const ReplicationQueryHandler &) = default;
-  ReplicationQueryHandler &operator=(const ReplicationQueryHandler &) = default;
+  CoordinatorQueryHandler(const CoordinatorQueryHandler &) = default;
+  CoordinatorQueryHandler &operator=(const CoordinatorQueryHandler &) = default;
 
-  ReplicationQueryHandler(ReplicationQueryHandler &&) = default;
-  ReplicationQueryHandler &operator=(ReplicationQueryHandler &&) = default;
+  CoordinatorQueryHandler(CoordinatorQueryHandler &&) = default;
+  CoordinatorQueryHandler &operator=(CoordinatorQueryHandler &&) = default;
 
   struct Replica {
     std::string name;
@@ -88,22 +93,32 @@ class ReplicationQueryHandler {
     ReplicationQuery::ReplicaState state;
   };
 
+#ifdef MG_ENTERPRISE
+  struct MainReplicaStatus {
+    std::string_view name;
+    std::string_view socket_address;
+    bool alive;
+    bool is_main;
+
+    MainReplicaStatus(std::string_view name, std::string_view socket_address, bool alive, bool is_main)
+        : name{name}, socket_address{socket_address}, alive{alive}, is_main{is_main} {}
+  };
+#endif
+
+#ifdef MG_ENTERPRISE
   /// @throw QueryRuntimeException if an error ocurred.
-  virtual void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) = 0;
+  virtual void RegisterInstance(const std::string &coordinator_socket_address,
+                                const std::string &replication_socket_address,
+                                const std::chrono::seconds instance_check_frequency, const std::string &instance_name,
+                                CoordinatorQuery::SyncMode sync_mode) = 0;
 
   /// @throw QueryRuntimeException if an error ocurred.
-  virtual ReplicationQuery::ReplicationRole ShowReplicationRole() const = 0;
+  virtual void SetInstanceToMain(const std::string &instance_name) = 0;
 
   /// @throw QueryRuntimeException if an error ocurred.
-  virtual void RegisterReplica(const std::string &name, const std::string &socket_address,
-                               ReplicationQuery::SyncMode sync_mode,
-                               const std::chrono::seconds replica_check_frequency) = 0;
+  virtual std::vector<coordination::CoordinatorInstanceStatus> ShowInstances() const = 0;
 
-  /// @throw QueryRuntimeException if an error ocurred.
-  virtual void DropReplica(std::string_view replica_name) = 0;
-
-  /// @throw QueryRuntimeException if an error ocurred.
-  virtual std::vector<Replica> ShowReplicas() const = 0;
+#endif
 };
 
 class AnalyzeGraphQueryHandler {
@@ -281,7 +296,38 @@ class Interpreter final {
 
   void SetUser(std::string_view username);
 
+  struct SystemTransactionGuard {
+    explicit SystemTransactionGuard(std::unique_lock<utils::ResourceLock> guard, dbms::DbmsHandler &dbms_handler)
+        : system_guard_(std::move(guard)), dbms_handler_{&dbms_handler} {
+      dbms_handler_->NewSystemTransaction();
+    }
+    SystemTransactionGuard &operator=(SystemTransactionGuard &&) = default;
+    SystemTransactionGuard(SystemTransactionGuard &&) = default;
+
+    ~SystemTransactionGuard() {
+      if (system_guard_.owns_lock()) dbms_handler_->ResetSystemTransaction();
+    }
+
+    dbms::AllSyncReplicaStatus Commit() { return dbms_handler_->Commit(); }
+
+   private:
+    std::unique_lock<utils::ResourceLock> system_guard_;
+    dbms::DbmsHandler *dbms_handler_;
+  };
+
+  std::optional<SystemTransactionGuard> system_transaction_guard_{};
+
  private:
+  void ResetInterpreter() {
+    query_executions_.clear();
+    system_guard.reset();
+    system_transaction_guard_.reset();
+    transaction_queries_->clear();
+    if (current_db_.db_acc_ && current_db_.db_acc_->is_deleting()) {
+      current_db_.db_acc_.reset();
+    }
+  }
+
   struct QueryExecution {
     std::variant<utils::MonotonicBufferResource, utils::PoolResource> execution_memory;
     utils::ResourceWithOutOfMemoryException execution_memory_with_exception;
@@ -340,6 +386,9 @@ class Interpreter final {
   // TODO Figure out how this would work for multi-database
   // Exists only during a single transaction (for now should be okay as is)
   std::vector<std::unique_ptr<QueryExecution>> query_executions_;
+  // TODO: our upgradable lock guard for system
+  std::optional<utils::ResourceLockGuard> system_guard;
+
   // all queries that are run as part of the current transaction
   utils::Synchronized<std::vector<std::string>, utils::SpinLock> transaction_queries_;
 
@@ -435,8 +484,7 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std:
         // NOTE: we cannot clear query_execution inside the Abort and Commit
         // methods as we will delete summary contained in them which we need
         // after our query finished executing.
-        query_executions_.clear();
-        transaction_queries_->clear();
+        ResetInterpreter();
       } else {
         // We can only clear this execution as some of the queries
         // in the transaction can be in unfinished state
diff --git a/src/query/interpreter_context.cpp b/src/query/interpreter_context.cpp
index 75d734645..cace25ec6 100644
--- a/src/query/interpreter_context.cpp
+++ b/src/query/interpreter_context.cpp
@@ -56,7 +56,7 @@ std::vector<std::vector<TypedValue>> InterpreterContext::TerminateTransactions(
         std::iter_swap(it, not_found_midpoint);
         auto get_interpreter_db_name = [&]() -> std::string const & {
           static std::string all;
-          return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->id() : all;
+          return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->name() : all;
         };
         if (interpreter->username_ == username || privilege_checker(get_interpreter_db_name())) {
           killed = true;  // Note: this is used by the above `clean_status` (OnScopeExit)
diff --git a/src/query/interpreter_context.hpp b/src/query/interpreter_context.hpp
index af8648376..9b54dbd3a 100644
--- a/src/query/interpreter_context.hpp
+++ b/src/query/interpreter_context.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,6 +22,8 @@
 #include "query/cypher_query_interpreter.hpp"
 #include "query/typed_value.hpp"
 #include "replication/state.hpp"
+#include "storage/v2/config.hpp"
+#include "storage/v2/transaction.hpp"
 #include "utils/gatekeeper.hpp"
 #include "utils/skip_list.hpp"
 #include "utils/spin_lock.hpp"
@@ -57,6 +59,7 @@ struct InterpreterContext {
 
   // GLOBAL
   memgraph::replication::ReplicationState *repl_state;
+
   AuthQueryHandler *auth;
   AuthChecker *auth_checker;
 
diff --git a/src/query/metadata.cpp b/src/query/metadata.cpp
index ade17eb5c..56ef57431 100644
--- a/src/query/metadata.cpp
+++ b/src/query/metadata.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -66,6 +66,10 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
       return "PlanHinting"sv;
     case NotificationCode::REGISTER_REPLICA:
       return "RegisterReplica"sv;
+#ifdef MG_ENTERPRISE
+    case NotificationCode::REGISTER_COORDINATOR_SERVER:
+      return "RegisterCoordinatorServer"sv;
+#endif
     case NotificationCode::REPLICA_PORT_WARNING:
       return "ReplicaPortWarning"sv;
     case NotificationCode::SET_REPLICA:
diff --git a/src/query/metadata.hpp b/src/query/metadata.hpp
index ca3914047..8e82ad1e3 100644
--- a/src/query/metadata.hpp
+++ b/src/query/metadata.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -42,6 +42,9 @@ enum class NotificationCode : uint8_t {
   PLAN_HINTING,
   REPLICA_PORT_WARNING,
   REGISTER_REPLICA,
+#ifdef MG_ENTERPRISE
+  REGISTER_COORDINATOR_SERVER,
+#endif
   SET_REPLICA,
   START_STREAM,
   START_ALL_STREAMS,
diff --git a/src/query/plan/cost_estimator.hpp b/src/query/plan/cost_estimator.hpp
index 47da0a23b..ede4a89fc 100644
--- a/src/query/plan/cost_estimator.hpp
+++ b/src/query/plan/cost_estimator.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -14,6 +14,7 @@
 #include "query/frontend/ast/ast.hpp"
 #include "query/parameters.hpp"
 #include "query/plan/operator.hpp"
+#include "query/plan/rewrite/index_lookup.hpp"
 #include "query/typed_value.hpp"
 #include "utils/algorithm.hpp"
 #include "utils/math.hpp"
@@ -46,6 +47,11 @@ struct CostEstimation {
   double cardinality;
 };
 
+struct PlanCost {
+  double cost;
+  bool use_index_hints;
+};
+
 /**
  * Query plan execution time cost estimator, for comparing and choosing optimal
  * execution plans.
@@ -109,11 +115,13 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
   using HierarchicalLogicalOperatorVisitor::PostVisit;
   using HierarchicalLogicalOperatorVisitor::PreVisit;
 
-  CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters &parameters)
-      : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{Scope()} {}
+  CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters &parameters,
+                const IndexHints &index_hints)
+      : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{Scope()}, index_hints_(index_hints) {}
 
-  CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters &parameters, Scope scope)
-      : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{scope} {}
+  CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters &parameters, Scope scope,
+                const IndexHints &index_hints)
+      : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{scope}, index_hints_(index_hints) {}
 
   bool PostVisit(ScanAll &) override {
     cardinality_ *= db_accessor_->VerticesCount();
@@ -129,7 +137,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
     }
 
     cardinality_ *= db_accessor_->VerticesCount(scan_all_by_label.label_);
-    // ScanAll performs some work for every element that is produced
+    if (index_hints_.HasLabelIndex(db_accessor_, scan_all_by_label.label_)) {
+      use_index_hints_ = true;
+    }
+
     IncrementCost(CostParam::kScanAllByLabel);
     return true;
   }
@@ -154,6 +165,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
 
     cardinality_ *= factor;
 
+    if (index_hints_.HasLabelPropertyIndex(db_accessor_, logical_op.label_, logical_op.property_)) {
+      use_index_hints_ = true;
+    }
+
     // ScanAll performs some work for every element that is produced
     IncrementCost(CostParam::MakeScanAllByLabelPropertyValue);
     return true;
@@ -184,6 +199,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
 
     cardinality_ *= factor;
 
+    if (index_hints_.HasLabelPropertyIndex(db_accessor_, logical_op.label_, logical_op.property_)) {
+      use_index_hints_ = true;
+    }
+
     // ScanAll performs some work for every element that is produced
     IncrementCost(CostParam::MakeScanAllByLabelPropertyRange);
     return true;
@@ -197,6 +216,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
 
     const auto factor = db_accessor_->VerticesCount(logical_op.label_, logical_op.property_);
     cardinality_ *= factor;
+    if (index_hints_.HasLabelPropertyIndex(db_accessor_, logical_op.label_, logical_op.property_)) {
+      use_index_hints_ = true;
+    }
+
     IncrementCost(CostParam::MakeScanAllByLabelProperty);
     return true;
   }
@@ -375,6 +398,7 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
 
   auto cost() const { return cost_; }
   auto cardinality() const { return cardinality_; }
+  auto use_index_hints() const { return use_index_hints_; }
 
  private:
   // cost estimation that gets accumulated as the visitor
@@ -390,17 +414,19 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
   const SymbolTable &table_;
   const Parameters &parameters;
   std::vector<Scope> scopes_;
+  IndexHints index_hints_;
+  bool use_index_hints_{false};
 
   void IncrementCost(double param) { cost_ += param * cardinality_; }
 
   CostEstimation EstimateCostOnBranch(std::shared_ptr<LogicalOperator> *branch) {
-    CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters);
+    CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters, index_hints_);
     (*branch)->Accept(cost_estimator);
     return CostEstimation{.cost = cost_estimator.cost(), .cardinality = cost_estimator.cardinality()};
   }
 
   CostEstimation EstimateCostOnBranch(std::shared_ptr<LogicalOperator> *branch, Scope scope) {
-    CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters, scope);
+    CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters, scope, index_hints_);
     (*branch)->Accept(cost_estimator);
     return CostEstimation{.cost = cost_estimator.cost(), .cardinality = cost_estimator.cardinality()};
   }
@@ -450,11 +476,11 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
 
 /** Returns the estimated cost of the given plan. */
 template <class TDbAccessor>
-double EstimatePlanCost(TDbAccessor *db, const SymbolTable &table, const Parameters &parameters,
-                        LogicalOperator &plan) {
-  CostEstimator<TDbAccessor> estimator(db, table, parameters);
+PlanCost EstimatePlanCost(TDbAccessor *db, const SymbolTable &table, const Parameters &parameters,
+                          LogicalOperator &plan, const IndexHints &index_hints) {
+  CostEstimator<TDbAccessor> estimator(db, table, parameters, index_hints);
   plan.Accept(estimator);
-  return estimator.cost();
+  return PlanCost{.cost = estimator.cost(), .use_index_hints = estimator.use_index_hints()};
 }
 
 }  // namespace memgraph::query::plan
diff --git a/src/query/plan/operator.hpp b/src/query/plan/operator.hpp
index 8fa3d3a7c..516ef2e38 100644
--- a/src/query/plan/operator.hpp
+++ b/src/query/plan/operator.hpp
@@ -916,11 +916,11 @@ struct ExpansionLambda {
   /// Currently expanded node symbol.
   Symbol inner_node_symbol;
   /// Expression used in lambda during expansion.
-  Expression *expression;
+  Expression *expression = nullptr;
   /// Currently expanded accumulated path symbol.
-  std::optional<Symbol> accumulated_path_symbol;
+  std::optional<Symbol> accumulated_path_symbol = std::nullopt;
   /// Currently expanded accumulated weight symbol.
-  std::optional<Symbol> accumulated_weight_symbol;
+  std::optional<Symbol> accumulated_weight_symbol = std::nullopt;
 
   ExpansionLambda Clone(AstStorage *storage) const {
     ExpansionLambda object;
diff --git a/src/query/plan/planner.hpp b/src/query/plan/planner.hpp
index 10318e6b9..e8ca80e39 100644
--- a/src/query/plan/planner.hpp
+++ b/src/query/plan/planner.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -59,9 +59,9 @@ class PostProcessor final {
   }
 
   template <class TVertexCounts>
-  double EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts,
-                          const SymbolTable &table) {
-    return query::plan::EstimatePlanCost(vertex_counts, table, parameters_, *plan);
+  PlanCost EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts,
+                            const SymbolTable &table) {
+    return query::plan::EstimatePlanCost(vertex_counts, table, parameters_, *plan, index_hints_);
   }
 };
 
@@ -99,6 +99,7 @@ auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process,
   auto query_parts = CollectQueryParts(*context->symbol_table, *context->ast_storage, context->query);
   auto &vertex_counts = *context->db;
   double total_cost = std::numeric_limits<double>::max();
+  bool curr_uses_index_hint = false;
 
   using ProcessedPlan = typename TPlanPostProcess::ProcessedPlan;
   ProcessedPlan plan_with_least_cost;
@@ -110,16 +111,28 @@ auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process,
       // Plans are generated lazily and the current plan will disappear, so
       // it's ok to move it.
       auto rewritten_plan = post_process->Rewrite(std::move(plan), context);
-      double cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table);
-      if (!curr_plan || cost < total_cost) {
+      auto plan_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table);
+      // if we have a plan that uses index hints, we reject all the plans that don't use index hinting because we want
+      // to force the plan using the index hints to be executed
+      if (curr_uses_index_hint && !plan_cost.use_index_hints) continue;
+      // if a plan uses index hints, and there is currently not yet a plan that utilizes it, we will take it regardless
+      if (plan_cost.use_index_hints && !curr_uses_index_hint) {
+        curr_uses_index_hint = plan_cost.use_index_hints;
         curr_plan.emplace(std::move(rewritten_plan));
-        total_cost = cost;
+        total_cost = plan_cost.cost;
+        continue;
+      }
+      // if both plans either use or don't use index hints, we want to use the one with the least cost
+      if (!curr_plan || plan_cost.cost < total_cost) {
+        curr_uses_index_hint = plan_cost.use_index_hints;
+        curr_plan.emplace(std::move(rewritten_plan));
+        total_cost = plan_cost.cost;
       }
     }
   } else {
     auto plan = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(query_parts, context);
     auto rewritten_plan = post_process->Rewrite(std::move(plan), context);
-    total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table);
+    total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table).cost;
     curr_plan.emplace(std::move(rewritten_plan));
   }
 
diff --git a/src/query/plan/preprocess.cpp b/src/query/plan/preprocess.cpp
index 22899cbc0..cf8ad9c97 100644
--- a/src/query/plan/preprocess.cpp
+++ b/src/query/plan/preprocess.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -257,7 +257,7 @@ void Filters::EraseFilter(const FilterInfo &filter) {
                      all_filters_.end());
 }
 
-void Filters::EraseLabelFilter(const Symbol &symbol, LabelIx label, std::vector<Expression *> *removed_filters) {
+void Filters::EraseLabelFilter(const Symbol &symbol, const LabelIx &label, std::vector<Expression *> *removed_filters) {
   for (auto filter_it = all_filters_.begin(); filter_it != all_filters_.end();) {
     if (filter_it->type != FilterInfo::Type::Label) {
       ++filter_it;
diff --git a/src/query/plan/preprocess.hpp b/src/query/plan/preprocess.hpp
index 8e1955907..2b53fb7b0 100644
--- a/src/query/plan/preprocess.hpp
+++ b/src/query/plan/preprocess.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -230,6 +230,7 @@ class PatternFilterVisitor : public ExpressionVisitor<void> {
   void Visit(ParameterLookup &op) override{};
   void Visit(NamedExpression &op) override{};
   void Visit(RegexMatch &op) override{};
+  void Visit(PatternComprehension &op) override{};
 
   std::vector<FilterMatching> getMatchings() { return matchings_; }
 
@@ -365,7 +366,8 @@ class Filters final {
   /// Remove a label filter for symbol; may invalidate iterators.
   /// If removed_filters is not nullptr, fills the vector with original
   /// `Expression *` which are now completely removed.
-  void EraseLabelFilter(const Symbol &, LabelIx, std::vector<Expression *> *removed_filters = nullptr);
+  void EraseLabelFilter(const Symbol &symbol, const LabelIx &label,
+                        std::vector<Expression *> *removed_filters = nullptr);
 
   /// Returns a vector of FilterInfo for properties.
   auto PropertyFilters(const Symbol &symbol) const {
diff --git a/src/query/plan/rewrite/index_lookup.hpp b/src/query/plan/rewrite/index_lookup.hpp
index 590bad5f4..09c6e2014 100644
--- a/src/query/plan/rewrite/index_lookup.hpp
+++ b/src/query/plan/rewrite/index_lookup.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -28,6 +28,7 @@
 
 #include "query/plan/operator.hpp"
 #include "query/plan/preprocess.hpp"
+#include "storage/v2/id_types.hpp"
 
 DECLARE_int64(query_vertex_count_to_expand_existing);
 
@@ -59,6 +60,29 @@ struct IndexHints {
     }
   }
 
+  template <class TDbAccessor>
+  bool HasLabelIndex(TDbAccessor *db, storage::LabelId label) const {
+    for (const auto &[index_type, label_hint, _] : label_index_hints_) {
+      auto label_id = db->NameToLabel(label_hint.name);
+      if (label_id == label) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  template <class TDbAccessor>
+  bool HasLabelPropertyIndex(TDbAccessor *db, storage::LabelId label, storage::PropertyId property) const {
+    for (const auto &[index_type, label_hint, property_hint] : label_property_index_hints_) {
+      auto label_id = db->NameToLabel(label_hint.name);
+      auto property_id = db->NameToProperty(property_hint->name);
+      if (label_id == label && property_id == property) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   std::vector<IndexHint> label_index_hints_{};
   std::vector<IndexHint> label_property_index_hints_{};
 };
@@ -631,9 +655,9 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
     }
   }
 
-  storage::LabelId GetLabel(LabelIx label) { return db_->NameToLabel(label.name); }
+  storage::LabelId GetLabel(const LabelIx &label) { return db_->NameToLabel(label.name); }
 
-  storage::PropertyId GetProperty(PropertyIx prop) { return db_->NameToProperty(prop.name); }
+  storage::PropertyId GetProperty(const PropertyIx &prop) { return db_->NameToProperty(prop.name); }
 
   std::optional<LabelIx> FindBestLabelIndex(const std::unordered_set<LabelIx> &labels) {
     MG_ASSERT(!labels.empty(), "Trying to find the best label without any labels.");
diff --git a/src/query/plan/rule_based_planner.cpp b/src/query/plan/rule_based_planner.cpp
index f3d0c1487..bf5e66158 100644
--- a/src/query/plan/rule_based_planner.cpp
+++ b/src/query/plan/rule_based_planner.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -373,12 +373,12 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
     return true;
   }
 
-  bool Visit(ParameterLookup &) override {
+  bool Visit(ParameterLookup & /*unused*/) override {
     has_aggregation_.emplace_back(false);
     return true;
   }
 
-  bool PostVisit(RegexMatch &regex_match) override {
+  bool PostVisit(RegexMatch & /*unused*/) override {
     MG_ASSERT(has_aggregation_.size() >= 2U, "Expected 2 has_aggregation_ flags for RegexMatch arguments");
     bool has_aggr = has_aggregation_.back();
     has_aggregation_.pop_back();
@@ -386,6 +386,10 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
     return true;
   }
 
+  bool PostVisit(PatternComprehension & /*unused*/) override {
+    throw utils::NotYetImplemented("Planner can not handle pattern comprehension.");
+  }
+
   // Creates NamedExpression with an Identifier for each user declared symbol.
   // This should be used when body.all_identifiers is true, to generate
   // expressions for Produce operator.
diff --git a/src/query/plan/rule_based_planner.hpp b/src/query/plan/rule_based_planner.hpp
index 074bd1c88..092710628 100644
--- a/src/query/plan/rule_based_planner.hpp
+++ b/src/query/plan/rule_based_planner.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -271,9 +271,9 @@ class RuleBasedPlanner {
  private:
   TPlanningContext *context_;
 
-  storage::LabelId GetLabel(LabelIx label) { return context_->db->NameToLabel(label.name); }
+  storage::LabelId GetLabel(const LabelIx &label) { return context_->db->NameToLabel(label.name); }
 
-  storage::PropertyId GetProperty(PropertyIx prop) { return context_->db->NameToProperty(prop.name); }
+  storage::PropertyId GetProperty(const PropertyIx &prop) { return context_->db->NameToProperty(prop.name); }
 
   storage::EdgeTypeId GetEdgeType(EdgeTypeIx edge_type) { return context_->db->NameToEdgeType(edge_type.name); }
 
@@ -380,6 +380,7 @@ class RuleBasedPlanner {
     if (pattern.identifier_->user_declared_) {
       std::vector<Symbol> path_elements;
       for (const PatternAtom *atom : pattern.atoms_) path_elements.emplace_back(symbol_table.at(*atom->identifier_));
+      bound_symbols.insert(symbol_table.at(*pattern.identifier_));
       last_op = std::make_unique<ConstructNamedPath>(std::move(last_op), symbol_table.at(*pattern.identifier_),
                                                      path_elements);
     }
diff --git a/src/query/procedure/module.hpp b/src/query/procedure/module.hpp
index 8963173e7..41cda0ca6 100644
--- a/src/query/procedure/module.hpp
+++ b/src/query/procedure/module.hpp
@@ -169,7 +169,7 @@ class ModuleRegistry final {
   // mentioned library will be first performed in the already existing binded
   // libraries and then the global namespace.
   // RTLD_DEEPBIND => https://linux.die.net/man/3/dlopen
-  SharedLibraryHandle libstd_handle{"libstdc++.so.6", RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND, kLibstdcppWarning};
+  SharedLibraryHandle libstd_handle{"libstdc++.so.6", RTLD_NOW | RTLD_LOCAL, kLibstdcppWarning};
 #endif
   std::vector<std::filesystem::path> modules_dirs_;
   std::filesystem::path internal_module_dir_;
diff --git a/src/query/procedure/py_module.cpp b/src/query/procedure/py_module.cpp
index 3fd09b0ab..19393c4d0 100644
--- a/src/query/procedure/py_module.cpp
+++ b/src/query/procedure/py_module.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,6 +13,7 @@
 
 #include <datetime.h>
 #include <methodobject.h>
+#include <objimpl.h>
 #include <pyerrors.h>
 #include <array>
 #include <optional>
@@ -57,7 +58,6 @@ PyObject *gMgpValueConversionError{nullptr};     // NOLINT(cppcoreguidelines-avo
 PyObject *gMgpSerializationError{nullptr};       // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 PyObject *gMgpAuthorizationError{nullptr};       // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 
-constexpr bool kStartGarbageCollection{true};
 constexpr auto kMicrosecondsInMillisecond{1000};
 constexpr auto kMicrosecondsInSecond{1000000};
 
@@ -867,6 +867,27 @@ py::Object MgpListToPyTuple(mgp_list *list, PyObject *py_graph) {
   return MgpListToPyTuple(list, reinterpret_cast<PyGraph *>(py_graph));
 }
 
+void PyCollectGarbage() {
+  // NOTE: No need to call _Py_IsFinalizing(), we ensure
+  // Python GC thread is stopped before Py_Finalize() is called
+  // in memgraph.cpp
+  if (!Py_IsInitialized()) {
+    // Calling EnsureGIL will crash the program if this is true.
+    return;
+  }
+
+  auto gil = py::EnsureGIL();
+
+  py::Object gc(PyImport_ImportModule("gc"));
+  if (!gc) {
+    LOG_FATAL(py::FetchError().value());
+  }
+
+  if (!gc.CallMethod("collect")) {
+    LOG_FATAL(py::FetchError().value());
+  }
+}
+
 namespace {
 struct RecordFieldCache {
   PyObject *key;
@@ -1027,24 +1048,8 @@ std::optional<py::ExceptionInfo> AddMultipleBatchRecordsFromPython(mgp_result *r
   return std::nullopt;
 }
 
-std::function<void()> PyObjectCleanup(py::Object &py_object, bool start_gc) {
-  return [py_object, start_gc]() {
-    if (start_gc) {
-      // Run `gc.collect` (reference cycle-detection) explicitly, so that we are
-      // sure the procedure cleaned up everything it held references to. If the
-      // user stored a reference to one of our `_mgp` instances then the
-      // internally used `mgp_*` structs will stay unfreed and a memory leak
-      // will be reported at the end of the query execution.
-      py::Object gc(PyImport_ImportModule("gc"));
-      if (!gc) {
-        LOG_FATAL(py::FetchError().value());
-      }
-
-      if (!gc.CallMethod("collect")) {
-        LOG_FATAL(py::FetchError().value());
-      }
-    }
-
+std::function<void()> PyObjectCleanup(py::Object &py_object) {
+  return [py_object]() {
     // After making sure all references from our side have been cleared,
     // invalidate the `_mgp.Graph` object. If the user kept a reference to one
     // of our `_mgp` instances then this will prevent them from using those
@@ -1095,7 +1100,7 @@ void CallPythonProcedure(const py::Object &py_cb, mgp_list *args, mgp_graph *gra
   std::optional<std::string> maybe_msg;
   {
     py::Object py_graph(MakePyGraph(graph, memory));
-    utils::OnScopeExit clean_up(PyObjectCleanup(py_graph, !is_batched));
+    utils::OnScopeExit clean_up(PyObjectCleanup(py_graph));
     if (py_graph) {
       maybe_msg = error_to_msg(call(py_graph));
     } else {
@@ -1110,22 +1115,11 @@ void CallPythonProcedure(const py::Object &py_cb, mgp_list *args, mgp_graph *gra
 
 void CallPythonCleanup(const py::Object &py_cleanup) {
   auto gil = py::EnsureGIL();
-
   auto py_res = py_cleanup.Call();
-
-  py::Object gc(PyImport_ImportModule("gc"));
-  if (!gc) {
-    LOG_FATAL(py::FetchError().value());
-  }
-
-  if (!gc.CallMethod("collect")) {
-    LOG_FATAL(py::FetchError().value());
-  }
 }
 
 void CallPythonInitializer(const py::Object &py_initializer, mgp_list *args, mgp_graph *graph, mgp_memory *memory) {
   auto gil = py::EnsureGIL();
-
   auto error_to_msg = [](const std::optional<py::ExceptionInfo> &exc_info) -> std::optional<std::string> {
     if (!exc_info) return std::nullopt;
     // Here we tell the traceback formatter to skip the first line of the
@@ -1146,7 +1140,7 @@ void CallPythonInitializer(const py::Object &py_initializer, mgp_list *args, mgp
   std::optional<std::string> maybe_msg;
   {
     py::Object py_graph(MakePyGraph(graph, memory));
-    utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph, !kStartGarbageCollection));
+    utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph));
     if (py_graph) {
       maybe_msg = error_to_msg(call(py_graph));
     } else {
@@ -1194,8 +1188,8 @@ void CallPythonTransformation(const py::Object &py_cb, mgp_messages *msgs, mgp_g
     py::Object py_graph(MakePyGraph(graph, memory));
     py::Object py_messages(MakePyMessages(msgs, memory));
 
-    utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph, kStartGarbageCollection));
-    utils::OnScopeExit clean_up_messages(PyObjectCleanup(py_messages, kStartGarbageCollection));
+    utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph));
+    utils::OnScopeExit clean_up_messages(PyObjectCleanup(py_messages));
 
     if (py_graph && py_messages) {
       maybe_msg = error_to_msg(call(py_graph, py_messages));
@@ -1264,7 +1258,7 @@ void CallPythonFunction(const py::Object &py_cb, mgp_list *args, mgp_graph *grap
   std::optional<std::string> maybe_msg;
   {
     py::Object py_graph(MakePyGraph(graph, memory));
-    utils::OnScopeExit clean_up(PyObjectCleanup(py_graph, kStartGarbageCollection));
+    utils::OnScopeExit clean_up(PyObjectCleanup(py_graph));
     if (py_graph) {
       auto maybe_result = call(py_graph);
       if (!maybe_result.HasError()) {
diff --git a/src/query/procedure/py_module.hpp b/src/query/procedure/py_module.hpp
index 85ad82b3e..9cb22fe2c 100644
--- a/src/query/procedure/py_module.hpp
+++ b/src/query/procedure/py_module.hpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2023 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -79,4 +79,7 @@ py::Object ImportPyModule(const char *, mgp_module *);
 /// Return nullptr and set appropriate Python exception on failure.
 py::Object ReloadPyModule(PyObject *, mgp_module *);
 
+/// Call full python circular reference garbage collection (all generations)
+void PyCollectGarbage();
+
 }  // namespace memgraph::query::procedure
diff --git a/src/query/stream/streams.cpp b/src/query/stream/streams.cpp
index 896607e94..101ca592c 100644
--- a/src/query/stream/streams.cpp
+++ b/src/query/stream/streams.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -644,6 +644,25 @@ void Streams::Drop(const std::string &stream_name) {
   // TODO(antaljanosbenjamin) Release the transformation
 }
 
+void Streams::DropAll() {
+  streams_.WithLock([this](StreamsMap &streams) {
+    bool durability_ok = true;
+    for (auto &[name, stream] : streams) {
+      // streams_ is write locked, which means there is no access to it outside of this function, thus only the Test
+      // function can be executing with the consumer, nothing else.
+      // By acquiring the write lock here for the consumer, we make sure there is
+      // no running Test function for this consumer, therefore it can be erased.
+      std::visit([&](const auto &stream_data) { stream_data.stream_source->Lock(); }, stream);
+      if (!storage_.Delete(name)) {
+        durability_ok = false;
+      }
+    }
+
+    streams.clear();
+    return durability_ok;  // TODO: do we need special case for this cleanup if false
+  });
+}
+
 void Streams::Start(const std::string &stream_name) {
   auto locked_streams = streams_.Lock();
   auto it = GetStream(*locked_streams, stream_name);
diff --git a/src/query/stream/streams.hpp b/src/query/stream/streams.hpp
index 2c89341d1..bad1f8c98 100644
--- a/src/query/stream/streams.hpp
+++ b/src/query/stream/streams.hpp
@@ -110,6 +110,11 @@ class Streams final {
   /// @throws StreamsException if the stream doesn't exist or if the persisted metadata can't be deleted.
   void Drop(const std::string &stream_name);
 
+  /// Deletes all existing streams and all the data that was persisted.
+  ///
+  /// @throws StreamsException if the persisted metadata can't be deleted.
+  void DropAll();
+
   /// Start consuming from a stream.
   ///
   /// @param stream_name name of the stream that needs to be started
diff --git a/src/query/typed_value.cpp b/src/query/typed_value.cpp
index ea883e428..4cb79508e 100644
--- a/src/query/typed_value.cpp
+++ b/src/query/typed_value.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -125,9 +125,7 @@ TypedValue::TypedValue(storage::PropertyValue &&other, utils::MemoryResource *me
     case storage::PropertyValue::Type::List: {
       type_ = Type::List;
       auto &vec = other.ValueList();
-      new (&list_v) TVector(memory_);
-      list_v.reserve(vec.size());
-      for (auto &v : vec) list_v.emplace_back(std::move(v));
+      new (&list_v) TVector(std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()), memory_);
       break;
     }
     case storage::PropertyValue::Type::Map: {
@@ -324,13 +322,13 @@ TypedValue::operator storage::PropertyValue() const {
 
 #define DEFINE_VALUE_AND_TYPE_GETTERS(type_param, type_enum, field)                              \
   type_param &TypedValue::Value##type_enum() {                                                   \
-    if (type_ != Type::type_enum)                                                                \
+    if (type_ != Type::type_enum) [[unlikely]]                                                   \
       throw TypedValueException("TypedValue is of type '{}', not '{}'", type_, Type::type_enum); \
     return field;                                                                                \
   }                                                                                              \
                                                                                                  \
   const type_param &TypedValue::Value##type_enum() const {                                       \
-    if (type_ != Type::type_enum)                                                                \
+    if (type_ != Type::type_enum) [[unlikely]]                                                   \
       throw TypedValueException("TypedValue is of type '{}', not '{}'", type_, Type::type_enum); \
     return field;                                                                                \
   }                                                                                              \
diff --git a/src/replication/CMakeLists.txt b/src/replication/CMakeLists.txt
index 597ed096a..e19ba7061 100644
--- a/src/replication/CMakeLists.txt
+++ b/src/replication/CMakeLists.txt
@@ -5,10 +5,8 @@ target_sources(mg-replication
         include/replication/state.hpp
         include/replication/epoch.hpp
         include/replication/config.hpp
-        include/replication/mode.hpp
-        include/replication/messages.hpp
-        include/replication/role.hpp
         include/replication/status.hpp
+        include/replication/messages.hpp
         include/replication/replication_client.hpp
         include/replication/replication_server.hpp
 
@@ -25,6 +23,6 @@ target_include_directories(mg-replication PUBLIC include)
 
 find_package(fmt REQUIRED)
 target_link_libraries(mg-replication
-    PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk
+    PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk mg::io mg::repl_coord_glue
     PRIVATE fmt::fmt
 )
diff --git a/src/replication/include/replication/config.hpp b/src/replication/include/replication/config.hpp
index f98069955..822e09f72 100644
--- a/src/replication/include/replication/config.hpp
+++ b/src/replication/include/replication/config.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -15,7 +15,7 @@
 #include <cstdint>
 #include <optional>
 #include <string>
-#include "replication/mode.hpp"
+#include "replication_coordination_glue/mode.hpp"
 
 namespace memgraph::replication {
 
@@ -24,7 +24,7 @@ inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
 
 struct ReplicationClientConfig {
   std::string name;
-  ReplicationMode mode{};
+  replication_coordination_glue::ReplicationMode mode{};
   std::string ip_address;
   uint16_t port{};
 
@@ -40,7 +40,7 @@ struct ReplicationClientConfig {
     friend bool operator==(const SSL &, const SSL &) = default;
   };
 
-  std::optional<SSL> ssl;
+  std::optional<SSL> ssl{};
 
   friend bool operator==(ReplicationClientConfig const &, ReplicationClientConfig const &) = default;
 };
diff --git a/src/replication/include/replication/messages.hpp b/src/replication/include/replication/messages.hpp
index 57cf29351..b4e0b51c7 100644
--- a/src/replication/include/replication/messages.hpp
+++ b/src/replication/include/replication/messages.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -15,30 +15,33 @@
 #include "slk/serialization.hpp"
 
 namespace memgraph::replication {
-
-struct FrequentHeartbeatReq {
-  static const utils::TypeInfo kType;                            // TODO: make constexpr?
-  static const utils::TypeInfo &GetTypeInfo() { return kType; }  // WHAT?
-
-  static void Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader);
-  static void Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder);
-  FrequentHeartbeatReq() = default;
-};
-
-struct FrequentHeartbeatRes {
+struct SystemHeartbeatReq {
   static const utils::TypeInfo kType;
   static const utils::TypeInfo &GetTypeInfo() { return kType; }
 
-  static void Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
-  static void Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
-  FrequentHeartbeatRes() = default;
-  explicit FrequentHeartbeatRes(bool success) : success(success) {}
-
-  bool success;
+  static void Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader);
+  static void Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder);
+  SystemHeartbeatReq() = default;
 };
 
-using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>;
+struct SystemHeartbeatRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
 
-void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
+  static void Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
+  static void Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
+  SystemHeartbeatRes() = default;
+  explicit SystemHeartbeatRes(uint64_t system_timestamp) : system_timestamp(system_timestamp) {}
 
+  uint64_t system_timestamp;
+};
+
+using SystemHeartbeatRpc = rpc::RequestResponse<SystemHeartbeatReq, SystemHeartbeatRes>;
 }  // namespace memgraph::replication
+
+namespace memgraph::slk {
+void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
+void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
+void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/);
+void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/);
+}  // namespace memgraph::slk
diff --git a/src/replication/include/replication/replication_client.hpp b/src/replication/include/replication/replication_client.hpp
index 16e1010bf..0c64ae625 100644
--- a/src/replication/include/replication/replication_client.hpp
+++ b/src/replication/include/replication/replication_client.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,9 +12,10 @@
 #pragma once
 
 #include "replication/config.hpp"
-#include "replication/messages.hpp"
+#include "replication_coordination_glue/messages.hpp"
 #include "rpc/client.hpp"
 #include "utils/scheduler.hpp"
+#include "utils/synchronized.hpp"
 #include "utils/thread_pool.hpp"
 
 #include <concepts>
@@ -22,8 +23,10 @@
 
 namespace memgraph::replication {
 
+struct ReplicationClient;
+
 template <typename F>
-concept InvocableWithStringView = std::invocable<F, std::string_view>;
+concept FrequentCheckCB = std::invocable<F, bool, ReplicationClient &>;
 
 struct ReplicationClient {
   explicit ReplicationClient(const memgraph::replication::ReplicationClientConfig &config);
@@ -34,24 +37,27 @@ struct ReplicationClient {
   ReplicationClient(ReplicationClient &&) noexcept = delete;
   ReplicationClient &operator=(ReplicationClient &&) noexcept = delete;
 
-  template <InvocableWithStringView F>
+  template <FrequentCheckCB F>
   void StartFrequentCheck(F &&callback) {
     // Help the user to get the most accurate replica state possible.
     if (replica_check_frequency_ > std::chrono::seconds(0)) {
-      replica_checker_.Run("Replica Checker", replica_check_frequency_, [this, cb = std::forward<F>(callback)] {
-        try {
-          bool success = false;
-          {
-            auto stream{rpc_client_.Stream<memgraph::replication::FrequentHeartbeatRpc>()};
-            success = stream.AwaitResponse().success;
-          }
-          if (success) {
-            cb(name_);
-          }
-        } catch (const rpc::RpcFailedException &) {
-          // Nothing to do...wait for a reconnect
-        }
-      });
+      replica_checker_.Run("Replica Checker", replica_check_frequency_,
+                           [this, cb = std::forward<F>(callback), reconnect = false]() mutable {
+                             try {
+                               {
+                                 auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
+                                 stream.AwaitResponse();
+                               }
+                               cb(reconnect, *this);
+                               reconnect = false;
+                             } catch (const rpc::RpcFailedException &) {
+                               // Nothing to do...wait for a reconnect
+                               // NOTE: Here we are communicating with the instance connection.
+                               //       We don't have access to the undelying client; so the only thing we can do it
+                               //       tell the callback that this is a reconnection and to check the state
+                               reconnect = true;
+                             }
+                           });
     }
   }
 
@@ -60,7 +66,14 @@ struct ReplicationClient {
   rpc::Client rpc_client_;
   std::chrono::seconds replica_check_frequency_;
 
-  memgraph::replication::ReplicationMode mode_{memgraph::replication::ReplicationMode::SYNC};
+  // TODO: Better, this was the easiest place to put this
+  enum class State {
+    BEHIND,
+    READY,
+  };
+  utils::Synchronized<State> state_{State::BEHIND};
+
+  replication_coordination_glue::ReplicationMode mode_{replication_coordination_glue::ReplicationMode::SYNC};
   // This thread pool is used for background tasks so we don't
   // block the main storage thread
   // We use only 1 thread for 2 reasons:
diff --git a/src/replication/include/replication/state.hpp b/src/replication/include/replication/state.hpp
index 76aec1053..a53885aff 100644
--- a/src/replication/include/replication/state.hpp
+++ b/src/replication/include/replication/state.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -14,9 +14,9 @@
 #include "kvstore/kvstore.hpp"
 #include "replication/config.hpp"
 #include "replication/epoch.hpp"
-#include "replication/mode.hpp"
 #include "replication/replication_client.hpp"
-#include "replication/role.hpp"
+#include "replication_coordination_glue/mode.hpp"
+#include "replication_coordination_glue/role.hpp"
 #include "replication_server.hpp"
 #include "status.hpp"
 #include "utils/result.hpp"
@@ -32,7 +32,8 @@ namespace memgraph::replication {
 
 enum class RolePersisted : uint8_t { UNKNOWN_OR_NO, YES };
 
-enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, COULD_NOT_BE_PERSISTED, NOT_MAIN, SUCCESS };
+// TODO: (andi) Rename Error to Status
+enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, COULD_NOT_BE_PERSISTED, NOT_MAIN, SUCCESS };
 
 struct RoleMainData {
   RoleMainData() = default;
@@ -45,7 +46,7 @@ struct RoleMainData {
   RoleMainData &operator=(RoleMainData &&) = default;
 
   ReplicationEpoch epoch_;
-  std::list<ReplicationClient> registered_replicas_{};
+  std::list<ReplicationClient> registered_replicas_{};  // TODO: data race issues
 };
 
 struct RoleReplicaData {
@@ -72,14 +73,16 @@ struct ReplicationState {
   using FetchReplicationResult_t = utils::BasicResult<FetchReplicationError, ReplicationData_t>;
   auto FetchReplicationData() -> FetchReplicationResult_t;
 
-  auto GetRole() const -> ReplicationRole {
-    return std::holds_alternative<RoleReplicaData>(replication_data_) ? ReplicationRole::REPLICA
-                                                                      : ReplicationRole::MAIN;
+  auto GetRole() const -> replication_coordination_glue::ReplicationRole {
+    return std::holds_alternative<RoleReplicaData>(replication_data_)
+               ? replication_coordination_glue::ReplicationRole::REPLICA
+               : replication_coordination_glue::ReplicationRole::MAIN;
   }
-  bool IsMain() const { return GetRole() == ReplicationRole::MAIN; }
-  bool IsReplica() const { return GetRole() == ReplicationRole::REPLICA; }
+  bool IsMain() const { return GetRole() == replication_coordination_glue::ReplicationRole::MAIN; }
+  bool IsReplica() const { return GetRole() == replication_coordination_glue::ReplicationRole::REPLICA; }
+
+  bool HasDurability() const { return nullptr != durability_; }
 
-  bool ShouldPersist() const { return nullptr != durability_; }
   bool TryPersistRoleMain(std::string new_epoch);
   bool TryPersistRoleReplica(const ReplicationServerConfig &config);
   bool TryPersistUnregisterReplica(std::string_view name);
@@ -91,7 +94,6 @@ struct ReplicationState {
   utils::BasicResult<RegisterReplicaError, ReplicationClient *> RegisterReplica(const ReplicationClientConfig &config);
 
   bool SetReplicationRoleMain();
-
   bool SetReplicationRoleReplica(const ReplicationServerConfig &config);
 
  private:
diff --git a/src/replication/include/replication/status.hpp b/src/replication/include/replication/status.hpp
index 943db423a..4dfba6aaa 100644
--- a/src/replication/include/replication/status.hpp
+++ b/src/replication/include/replication/status.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -21,7 +21,7 @@
 
 #include "replication/config.hpp"
 #include "replication/epoch.hpp"
-#include "replication/role.hpp"
+#include "replication_coordination_glue/role.hpp"
 
 namespace memgraph::replication::durability {
 
@@ -42,7 +42,7 @@ struct MainRole {
 
 // fragment of key: "__replication_role"
 struct ReplicaRole {
-  ReplicationServerConfig config;
+  ReplicationServerConfig config{};
   friend bool operator==(ReplicaRole const &, ReplicaRole const &) = default;
 };
 
diff --git a/src/replication/messages.cpp b/src/replication/messages.cpp
index 4503e9df2..b2dca374e 100644
--- a/src/replication/messages.cpp
+++ b/src/replication/messages.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -8,58 +8,45 @@
 // the Business Source License, use of this software will be governed
 // by the Apache License, Version 2.0, included in the file
 // licenses/APL.txt.
-
 #include "replication/messages.hpp"
-#include "rpc/messages.hpp"
-#include "slk/serialization.hpp"
-#include "slk/streams.hpp"
-
-namespace memgraph::slk {
-// Serialize code for FrequentHeartbeatRes
-void Save(const memgraph::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.success, builder);
-}
-void Load(memgraph::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->success, reader);
-}
-
-// Serialize code for FrequentHeartbeatReq
-void Save(const memgraph::replication::FrequentHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) {
-  /* Nothing to serialize */
-}
-void Load(memgraph::replication::FrequentHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) {
-  /* Nothing to serialize */
-}
-
-}  // namespace memgraph::slk
 
 namespace memgraph::replication {
 
-constexpr utils::TypeInfo FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ, "FrequentHeartbeatReq",
-                                                      nullptr};
+constexpr utils::TypeInfo SystemHeartbeatReq::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_REQ, "SystemHeartbeatReq",
+                                                    nullptr};
 
-constexpr utils::TypeInfo FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES, "FrequentHeartbeatRes",
-                                                      nullptr};
+constexpr utils::TypeInfo SystemHeartbeatRes::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_RES, "SystemHeartbeatRes",
+                                                    nullptr};
 
-void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {
+void SystemHeartbeatReq::Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder) {
   memgraph::slk::Save(self, builder);
 }
-void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {
+void SystemHeartbeatReq::Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader) {
   memgraph::slk::Load(self, reader);
 }
-void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
+void SystemHeartbeatRes::Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
   memgraph::slk::Save(self, builder);
 }
-void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
+void SystemHeartbeatRes::Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
   memgraph::slk::Load(self, reader);
 }
 
-void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
-  FrequentHeartbeatReq req;
-  FrequentHeartbeatReq::Load(&req, req_reader);
-  memgraph::slk::Load(&req, req_reader);
-  FrequentHeartbeatRes res{true};
-  memgraph::slk::Save(res, res_builder);
-}
-
 }  // namespace memgraph::replication
+
+namespace memgraph::slk {
+// Serialize code for SystemHeartbeatRes
+void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.system_timestamp, builder);
+}
+void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->system_timestamp, reader);
+}
+
+// Serialize code for SystemHeartbeatReq
+void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) {
+  /* Nothing to serialize */
+}
+void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) {
+  /* Nothing to serialize */
+}
+}  // namespace memgraph::slk
diff --git a/src/replication/replication_client.cpp b/src/replication/replication_client.cpp
index d14250c2a..ed46ea471 100644
--- a/src/replication/replication_client.cpp
+++ b/src/replication/replication_client.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -28,8 +28,8 @@ ReplicationClient::ReplicationClient(const memgraph::replication::ReplicationCli
       mode_{config.mode} {}
 
 ReplicationClient::~ReplicationClient() {
-  auto endpoint = rpc_client_.Endpoint();
   try {
+    auto const &endpoint = rpc_client_.Endpoint();
     spdlog::trace("Closing replication client on {}:{}", endpoint.address, endpoint.port);
   } catch (...) {
     // Logging can throw. Not a big deal, just ignore.
diff --git a/src/replication/replication_server.cpp b/src/replication/replication_server.cpp
index f79ea2add..03c48d298 100644
--- a/src/replication/replication_server.cpp
+++ b/src/replication/replication_server.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,7 +10,7 @@
 // licenses/APL.txt.
 
 #include "replication/replication_server.hpp"
-#include "replication/messages.hpp"
+#include "replication_coordination_glue/messages.hpp"
 
 namespace memgraph::replication {
 namespace {
@@ -32,9 +32,9 @@ ReplicationServer::ReplicationServer(const memgraph::replication::ReplicationSer
     : rpc_server_context_{CreateServerContext(config)},
       rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
                   kReplicationServerThreads} {
-  rpc_server_.Register<FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
+  rpc_server_.Register<replication_coordination_glue::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
     spdlog::debug("Received FrequentHeartbeatRpc");
-    FrequentHeartbeatHandler(req_reader, res_builder);
+    replication_coordination_glue::FrequentHeartbeatHandler(req_reader, res_builder);
   });
 }
 
diff --git a/src/replication/state.cpp b/src/replication/state.cpp
index 60c390e17..d04a3d245 100644
--- a/src/replication/state.cpp
+++ b/src/replication/state.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -34,6 +34,7 @@ ReplicationState::ReplicationState(std::optional<std::filesystem::path> durabili
   repl_dir /= kReplicationDirectory;
   utils::EnsureDirOrDie(repl_dir);
   durability_ = std::make_unique<kvstore::KVStore>(std::move(repl_dir));
+  spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
 
   auto replicationData = FetchReplicationData();
   if (replicationData.HasError()) {
@@ -54,7 +55,7 @@ ReplicationState::ReplicationState(std::optional<std::filesystem::path> durabili
 }
 
 bool ReplicationState::TryPersistRoleReplica(const ReplicationServerConfig &config) {
-  if (!ShouldPersist()) return true;
+  if (!HasDurability()) return true;
 
   auto data = durability::ReplicationRoleEntry{.role = durability::ReplicaRole{
                                                    .config = config,
@@ -78,7 +79,7 @@ bool ReplicationState::TryPersistRoleReplica(const ReplicationServerConfig &conf
 }
 
 bool ReplicationState::TryPersistRoleMain(std::string new_epoch) {
-  if (!ShouldPersist()) return true;
+  if (!HasDurability()) return true;
 
   auto data =
       durability::ReplicationRoleEntry{.role = durability::MainRole{.epoch = ReplicationEpoch{std::move(new_epoch)}}};
@@ -92,7 +93,7 @@ bool ReplicationState::TryPersistRoleMain(std::string new_epoch) {
 }
 
 bool ReplicationState::TryPersistUnregisterReplica(std::string_view name) {
-  if (!ShouldPersist()) return true;
+  if (!HasDurability()) return true;
 
   auto key = BuildReplicaKey(name);
 
@@ -104,7 +105,7 @@ bool ReplicationState::TryPersistUnregisterReplica(std::string_view name) {
 // TODO: FetchEpochData (agnostic of FetchReplicationData, but should be done before)
 
 auto ReplicationState::FetchReplicationData() -> FetchReplicationResult_t {
-  if (!ShouldPersist()) return FetchReplicationError::NOTHING_FETCHED;
+  if (!HasDurability()) return FetchReplicationError::NOTHING_FETCHED;
   const auto replication_data = durability_->Get(durability::kReplicationRoleName);
   if (!replication_data.has_value()) {
     return FetchReplicationError::NOTHING_FETCHED;
@@ -199,7 +200,7 @@ bool ReplicationState::HandleVersionMigration(durability::ReplicationRoleEntry &
 }
 
 bool ReplicationState::TryPersistRegisteredReplica(const ReplicationClientConfig &config) {
-  if (!ShouldPersist()) return true;
+  if (!HasDurability()) return true;
 
   // If any replicas are persisted then Role must be persisted
   if (role_persisted != RolePersisted::YES) {
@@ -218,10 +219,12 @@ bool ReplicationState::TryPersistRegisteredReplica(const ReplicationClientConfig
 
 bool ReplicationState::SetReplicationRoleMain() {
   auto new_epoch = utils::GenerateUUID();
+
   if (!TryPersistRoleMain(new_epoch)) {
     return false;
   }
   replication_data_ = RoleMainData{ReplicationEpoch{new_epoch}};
+
   return true;
 }
 
@@ -236,6 +239,7 @@ bool ReplicationState::SetReplicationRoleReplica(const ReplicationServerConfig &
 utils::BasicResult<RegisterReplicaError, ReplicationClient *> ReplicationState::RegisterReplica(
     const ReplicationClientConfig &config) {
   auto const replica_handler = [](RoleReplicaData const &) { return RegisterReplicaError::NOT_MAIN; };
+
   ReplicationClient *client{nullptr};
   auto const main_handler = [&client, &config, this](RoleMainData &mainData) -> RegisterReplicaError {
     // name check
@@ -256,7 +260,7 @@ utils::BasicResult<RegisterReplicaError, ReplicationClient *> ReplicationState::
       return std::any_of(replicas.begin(), replicas.end(), endpoint_matches);
     };
     if (endpoint_check(mainData.registered_replicas_)) {
-      return RegisterReplicaError::END_POINT_EXISTS;
+      return RegisterReplicaError::ENDPOINT_EXISTS;
     }
 
     // Durability
@@ -275,4 +279,5 @@ utils::BasicResult<RegisterReplicaError, ReplicationClient *> ReplicationState::
   }
   return res;
 }
+
 }  // namespace memgraph::replication
diff --git a/src/replication/status.cpp b/src/replication/status.cpp
index 06d67cc66..de1af9589 100644
--- a/src/replication/status.cpp
+++ b/src/replication/status.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -29,12 +29,14 @@ constexpr auto *kVersion = "durability_version";
 
 void to_json(nlohmann::json &j, const ReplicationRoleEntry &p) {
   auto processMAIN = [&](MainRole const &main) {
-    j = nlohmann::json{{kVersion, p.version}, {kReplicationRole, ReplicationRole::MAIN}, {kEpoch, main.epoch.id()}};
+    j = nlohmann::json{{kVersion, p.version},
+                       {kReplicationRole, replication_coordination_glue::ReplicationRole::MAIN},
+                       {kEpoch, main.epoch.id()}};
   };
   auto processREPLICA = [&](ReplicaRole const &replica) {
     j = nlohmann::json{
         {kVersion, p.version},
-        {kReplicationRole, ReplicationRole::REPLICA},
+        {kReplicationRole, replication_coordination_glue::ReplicationRole::REPLICA},
         {kIpAddress, replica.config.ip_address},
         {kPort, replica.config.port}
         // TODO: SSL
@@ -47,17 +49,17 @@ void from_json(const nlohmann::json &j, ReplicationRoleEntry &p) {
   // This value did not exist in V1, hence default DurabilityVersion::V1
   DurabilityVersion version = j.value(kVersion, DurabilityVersion::V1);
   // NOLINTNEXTLINE(cppcoreguidelines-init-variables)
-  ReplicationRole role;
+  replication_coordination_glue::ReplicationRole role;
   j.at(kReplicationRole).get_to(role);
   switch (role) {
-    case ReplicationRole::MAIN: {
+    case replication_coordination_glue::ReplicationRole::MAIN: {
       auto json_epoch = j.value(kEpoch, std::string{});
       auto epoch = ReplicationEpoch{};
       if (!json_epoch.empty()) epoch.SetEpoch(json_epoch);
       p = ReplicationRoleEntry{.version = version, .role = MainRole{.epoch = std::move(epoch)}};
       break;
     }
-    case ReplicationRole::REPLICA: {
+    case memgraph::replication_coordination_glue::ReplicationRole::REPLICA: {
       std::string ip_address;
       // NOLINTNEXTLINE(cppcoreguidelines-init-variables)
       uint16_t port;
@@ -95,7 +97,7 @@ void from_json(const nlohmann::json &j, ReplicationReplicaEntry &p) {
   auto seconds = j.at(kCheckFrequency).get<std::chrono::seconds::rep>();
   auto config = ReplicationClientConfig{
       .name = j.at(kReplicaName).get<std::string>(),
-      .mode = j.at(kSyncMode).get<ReplicationMode>(),
+      .mode = j.at(kSyncMode).get<replication_coordination_glue::ReplicationMode>(),
       .ip_address = j.at(kIpAddress).get<std::string>(),
       .port = j.at(kPort).get<uint16_t>(),
       .replica_check_frequency = std::chrono::seconds{seconds},
diff --git a/src/replication_coordination_glue/CMakeLists.txt b/src/replication_coordination_glue/CMakeLists.txt
new file mode 100644
index 000000000..010a7b596
--- /dev/null
+++ b/src/replication_coordination_glue/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_library(mg-repl_coord_glue STATIC )
+add_library(mg::repl_coord_glue ALIAS mg-repl_coord_glue)
+
+target_sources(mg-repl_coord_glue
+        PUBLIC
+        messages.hpp
+        mode.hpp
+        role.hpp
+
+        PRIVATE
+        messages.cpp
+)
+
+target_link_libraries(mg-repl_coord_glue mg-rpc mg-slk)
diff --git a/src/replication_coordination_glue/messages.cpp b/src/replication_coordination_glue/messages.cpp
new file mode 100644
index 000000000..c7cf0b15c
--- /dev/null
+++ b/src/replication_coordination_glue/messages.cpp
@@ -0,0 +1,63 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#include "replication_coordination_glue/messages.hpp"
+#include "rpc/messages.hpp"
+#include "slk/serialization.hpp"
+#include "slk/streams.hpp"
+
+namespace memgraph::slk {
+// Serialize code for FrequentHeartbeatRes
+void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {}
+void Load(memgraph::replication_coordination_glue::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {}
+
+// Serialize code for FrequentHeartbeatReq
+void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatReq & /*self*/,
+          memgraph::slk::Builder * /*builder*/) {
+  /* Nothing to serialize */
+}
+void Load(memgraph::replication_coordination_glue::FrequentHeartbeatReq * /*self*/,
+          memgraph::slk::Reader * /*reader*/) {
+  /* Nothing to serialize */
+}
+
+}  // namespace memgraph::slk
+
+namespace memgraph::replication_coordination_glue {
+
+constexpr utils::TypeInfo FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ, "FrequentHeartbeatReq",
+                                                      nullptr};
+
+constexpr utils::TypeInfo FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES, "FrequentHeartbeatRes",
+                                                      nullptr};
+
+void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+
+void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
+  FrequentHeartbeatReq req;
+  FrequentHeartbeatReq::Load(&req, req_reader);
+  memgraph::slk::Load(&req, req_reader);
+  FrequentHeartbeatRes res{};
+  memgraph::slk::Save(res, res_builder);
+}
+
+}  // namespace memgraph::replication_coordination_glue
diff --git a/src/replication_coordination_glue/messages.hpp b/src/replication_coordination_glue/messages.hpp
new file mode 100644
index 000000000..5e2ef0fdf
--- /dev/null
+++ b/src/replication_coordination_glue/messages.hpp
@@ -0,0 +1,49 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include "rpc/messages.hpp"
+#include "slk/serialization.hpp"
+
+namespace memgraph::replication_coordination_glue {
+
+struct FrequentHeartbeatReq {
+  static const utils::TypeInfo kType;  // TODO: make constexpr?
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader);
+  static void Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder);
+  FrequentHeartbeatReq() = default;
+};
+
+struct FrequentHeartbeatRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
+  static void Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
+  FrequentHeartbeatRes() = default;
+};
+
+using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>;
+
+void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
+
+}  // namespace memgraph::replication_coordination_glue
+
+namespace memgraph::slk {
+void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
+void Load(memgraph::replication_coordination_glue::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
+void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatReq & /*self*/,
+          memgraph::slk::Builder * /*builder*/);
+void Load(memgraph::replication_coordination_glue::FrequentHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/);
+}  // namespace memgraph::slk
diff --git a/src/replication/include/replication/mode.hpp b/src/replication_coordination_glue/mode.hpp
similarity index 80%
rename from src/replication/include/replication/mode.hpp
rename to src/replication_coordination_glue/mode.hpp
index c1afe2b1f..d0b415733 100644
--- a/src/replication/include/replication/mode.hpp
+++ b/src/replication_coordination_glue/mode.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,7 +10,9 @@
 // licenses/APL.txt.
 
 #pragma once
+
 #include <cstdint>
-namespace memgraph::replication {
+
+namespace memgraph::replication_coordination_glue {
 enum class ReplicationMode : std::uint8_t { SYNC, ASYNC };
-}
+}  // namespace memgraph::replication_coordination_glue
diff --git a/src/replication_coordination_glue/role.hpp b/src/replication_coordination_glue/role.hpp
new file mode 100644
index 000000000..d472cb454
--- /dev/null
+++ b/src/replication_coordination_glue/role.hpp
@@ -0,0 +1,19 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <cstdint>
+namespace memgraph::replication_coordination_glue {
+
+// TODO: figure out a way of ensuring that usage of this type is never uninitialed/defaulted incorrectly to MAIN
+enum class ReplicationRole : uint8_t { MAIN, REPLICA };
+}  // namespace memgraph::replication_coordination_glue
diff --git a/src/rpc/client.hpp b/src/rpc/client.hpp
index 1fd3fff8d..a9ae7202d 100644
--- a/src/rpc/client.hpp
+++ b/src/rpc/client.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -105,11 +105,15 @@ class Client {
       utils::OnScopeExit res_cleanup([&, response_data_size] { self_->client_->ShiftData(response_data_size); });
 
       utils::TypeId res_id{utils::TypeId::UNKNOWN};
-      slk::Load(&res_id, &res_reader);
-
       // NOLINTNEXTLINE(cppcoreguidelines-init-variables)
       rpc::Version version;
-      slk::Load(&version, &res_reader);
+
+      try {
+        slk::Load(&res_id, &res_reader);
+        slk::Load(&version, &res_reader);
+      } catch (const slk::SlkReaderException &) {
+        throw SlkRpcFailedException();
+      }
 
       if (version != rpc::current_version) {
         // V1 we introduced versioning with, absolutely no backwards compatibility,
diff --git a/src/rpc/exceptions.hpp b/src/rpc/exceptions.hpp
index 346c53a9a..f278b2414 100644
--- a/src/rpc/exceptions.hpp
+++ b/src/rpc/exceptions.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -45,4 +45,12 @@ class GenericRpcFailedException : public RpcFailedException {
   SPECIALIZE_GET_EXCEPTION_NAME(GenericRpcFailedException);
 };
 
+class SlkRpcFailedException : public RpcFailedException {
+ public:
+  SlkRpcFailedException()
+      : RpcFailedException("Received malformed message from cluster. Please raise an issue on Memgraph GitHub issues.") {}
+
+  SPECIALIZE_GET_EXCEPTION_NAME(SlkRpcFailedException);
+};
+
 }  // namespace memgraph::rpc
diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp
index 8bc77579b..2a9c8ea72 100644
--- a/src/rpc/protocol.cpp
+++ b/src/rpc/protocol.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,7 +13,7 @@
 
 #include <utility>
 
-#include "rpc/messages.hpp"
+#include "rpc/exceptions.hpp"
 #include "rpc/server.hpp"
 #include "rpc/version.hpp"
 #include "slk/serialization.hpp"
@@ -46,10 +46,14 @@ void Session::Execute() {
 
   // Load the request ID.
   utils::TypeId req_id{utils::TypeId::UNKNOWN};
-  slk::Load(&req_id, &req_reader);
   // NOLINTNEXTLINE(cppcoreguidelines-init-variables)
   rpc::Version version;
-  slk::Load(&version, &req_reader);
+  try {
+    slk::Load(&req_id, &req_reader);
+    slk::Load(&version, &req_reader);
+  } catch (const slk::SlkReaderException &) {
+    throw rpc::SlkRpcFailedException();
+  }
 
   if (version != rpc::current_version) {
     // V1 we introduced versioning with, absolutely no backwards compatibility,
@@ -76,12 +80,20 @@ void Session::Execute() {
     SPDLOG_TRACE("[RpcServer] received {}", extended_it->second.req_type.name);
     slk::Save(extended_it->second.res_type.id, &res_builder);
     slk::Save(rpc::current_version, &res_builder);
-    extended_it->second.callback(endpoint_, &req_reader, &res_builder);
+    try {
+      extended_it->second.callback(endpoint_, &req_reader, &res_builder);
+    } catch (const slk::SlkReaderException &) {
+      throw rpc::SlkRpcFailedException();
+    }
   } else {
     SPDLOG_TRACE("[RpcServer] received {}", it->second.req_type.name);
     slk::Save(it->second.res_type.id, &res_builder);
     slk::Save(rpc::current_version, &res_builder);
-    it->second.callback(&req_reader, &res_builder);
+    try {
+      it->second.callback(&req_reader, &res_builder);
+    } catch (const slk::SlkReaderException &) {
+      throw rpc::SlkRpcFailedException();
+    }
   }
 
   // Finalize the SLK streams.
diff --git a/src/rpc/version.hpp b/src/rpc/version.hpp
index 29e7f8d3a..b234a3ccc 100644
--- a/src/rpc/version.hpp
+++ b/src/rpc/version.hpp
@@ -22,6 +22,12 @@ using Version = uint64_t;
 // probability of accidental match/conformance with pre 2.13 versions
 constexpr auto v1 = Version{2023'10'30'0'2'13};
 
-constexpr auto current_version = v1;
+// TypeId has been changed, they were not stable
+// Added stable numbering for replication types to be in
+// 2000-2999 range. We shouldn't need to version bump again
+// for any TypeIds that get added.
+constexpr auto v2 = Version{2023'12'07'0'2'14};
+
+constexpr auto current_version = v2;
 
 }  // namespace memgraph::rpc
diff --git a/src/slk/serialization.hpp b/src/slk/serialization.hpp
index 9ca99527d..41c3d8539 100644
--- a/src/slk/serialization.hpp
+++ b/src/slk/serialization.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -55,11 +55,22 @@ class SlkDecodeException : public utils::BasicException {
 // here because C++ doesn't know how to resolve the function call if it isn't in
 // the global namespace.
 
+template <typename T>
+inline void Save(const std::vector<T> &obj, Builder *builder,
+                 std::function<void(const T &, Builder *)> item_save_function);
+template <typename T>
+inline void Load(std::vector<T> *obj, Reader *reader, std::function<void(T *, Reader *)> item_load_function);
+
 template <typename T>
 void Save(const std::vector<T> &obj, Builder *builder);
 template <typename T>
 void Load(std::vector<T> *obj, Reader *reader);
 
+template <typename T, size_t N>
+void Save(const std::array<T, N> &obj, Builder *builder);
+template <typename T, size_t N>
+void Load(std::array<T, N> *obj, Reader *reader);
+
 template <typename T, typename Cmp>
 void Save(const std::set<T, Cmp> &obj, Builder *builder);
 template <typename T, typename Cmp>
@@ -201,6 +212,24 @@ inline void Load(std::vector<T> *obj, Reader *reader) {
   }
 }
 
+template <typename T, size_t N>
+inline void Save(const std::array<T, N> &obj, Builder *builder) {
+  uint64_t size = obj.size();
+  Save(size, builder);
+  for (const auto &item : obj) {
+    Save(item, builder);
+  }
+}
+
+template <typename T, size_t N>
+inline void Load(std::array<T, N> *obj, Reader *reader) {
+  uint64_t size = 0;
+  Load(&size, reader);
+  for (uint64_t i = 0; i < size; ++i) {
+    Load(&(*obj)[i], reader);
+  }
+}
+
 template <typename T, typename Cmp>
 inline void Save(const std::set<T, Cmp> &obj, Builder *builder) {
   uint64_t size = obj.size();
@@ -486,4 +515,17 @@ inline void Load(utils::TypeId *obj, Reader *reader) {
   *obj = utils::TypeId(utils::MemcpyCast<enum_type>(obj_encoded));
 }
 
+template <utils::Enum T>
+void Save(const T &enum_value, slk::Builder *builder) {
+  slk::Save(utils::UnderlyingCast(enum_value), builder);
+}
+
+template <utils::Enum T>
+void Load(T *enum_value, slk::Reader *reader) {
+  using UnderlyingType = std::underlying_type_t<T>;
+  UnderlyingType value;
+  slk::Load(&value, reader);
+  *enum_value = static_cast<T>(value);
+}
+
 }  // namespace memgraph::slk
diff --git a/src/storage/v2/config.hpp b/src/storage/v2/config.hpp
index dee2afe87..3533594ce 100644
--- a/src/storage/v2/config.hpp
+++ b/src/storage/v2/config.hpp
@@ -14,10 +14,12 @@
 #include <chrono>
 #include <cstdint>
 #include <filesystem>
+
 #include "storage/v2/isolation_level.hpp"
 #include "storage/v2/storage_mode.hpp"
 #include "utils/exceptions.hpp"
 #include "utils/logging.hpp"
+#include "utils/uuid.hpp"
 
 namespace memgraph::storage {
 
@@ -27,6 +29,41 @@ class StorageConfigException : public utils::BasicException {
   SPECIALIZE_GET_EXCEPTION_NAME(StorageConfigException)
 };
 
+struct SalientConfig {
+  std::string name;
+  utils::UUID uuid;
+  StorageMode storage_mode{StorageMode::IN_MEMORY_TRANSACTIONAL};
+  struct Items {
+    bool properties_on_edges{true};
+    bool enable_schema_metadata{false};
+    friend bool operator==(const Items &lrh, const Items &rhs) = default;
+  } items;
+
+  friend bool operator==(const SalientConfig &, const SalientConfig &) = default;
+};
+
+inline void to_json(nlohmann::json &data, SalientConfig::Items const &items) {
+  data = nlohmann::json{{"properties_on_edges", items.properties_on_edges},
+                        {"enable_schema_metadata", items.enable_schema_metadata}};
+}
+
+inline void from_json(const nlohmann::json &data, SalientConfig::Items &items) {
+  data.at("properties_on_edges").get_to(items.properties_on_edges);
+  data.at("enable_schema_metadata").get_to(items.enable_schema_metadata);
+}
+
+inline void to_json(nlohmann::json &data, SalientConfig const &config) {
+  data = nlohmann::json{
+      {"items", config.items}, {"name", config.name}, {"uuid", config.uuid}, {"storage_mode", config.storage_mode}};
+}
+
+inline void from_json(const nlohmann::json &data, SalientConfig &config) {
+  data.at("items").get_to(config.items);
+  data.at("name").get_to(config.name);
+  data.at("uuid").get_to(config.uuid);
+  data.at("storage_mode").get_to(config.storage_mode);
+}
+
 /// Pass this class to the \ref Storage constructor to change the behavior of
 /// the storage. This class also defines the default behavior.
 struct Config {
@@ -36,46 +73,40 @@ struct Config {
     Type type{Type::PERIODIC};
     std::chrono::milliseconds interval{std::chrono::milliseconds(1000)};
     friend bool operator==(const Gc &lrh, const Gc &rhs) = default;
-  } gc;
-
-  struct Items {
-    bool properties_on_edges{true};
-    bool enable_schema_metadata{false};
-    friend bool operator==(const Items &lrh, const Items &rhs) = default;
-  } items;
+  } gc;  // SYSTEM FLAG
 
   struct Durability {
     enum class SnapshotWalMode { DISABLED, PERIODIC_SNAPSHOT, PERIODIC_SNAPSHOT_WITH_WAL };
 
-    std::filesystem::path storage_directory{"storage"};
+    std::filesystem::path storage_directory{"storage"};  // PER INSTANCE SYSTEM FLAG-> root folder...ish
 
-    bool recover_on_startup{false};
+    bool recover_on_startup{false};  // PER INSTANCE SYSTEM FLAG
 
-    SnapshotWalMode snapshot_wal_mode{SnapshotWalMode::DISABLED};
+    SnapshotWalMode snapshot_wal_mode{SnapshotWalMode::DISABLED};  // PER DATABASE
 
-    std::chrono::milliseconds snapshot_interval{std::chrono::minutes(2)};
-    uint64_t snapshot_retention_count{3};
+    std::chrono::milliseconds snapshot_interval{std::chrono::minutes(2)};  // PER DATABASE
+    uint64_t snapshot_retention_count{3};                                  // PER DATABASE
 
-    uint64_t wal_file_size_kibibytes{20 * 1024};
-    uint64_t wal_file_flush_every_n_tx{100000};
+    uint64_t wal_file_size_kibibytes{20 * 1024};  // PER DATABASE
+    uint64_t wal_file_flush_every_n_tx{100000};   // PER DATABASE
 
-    bool snapshot_on_exit{false};
-    bool restore_replication_state_on_startup{false};
+    bool snapshot_on_exit{false};                      // PER DATABASE
+    bool restore_replication_state_on_startup{false};  // PER INSTANCE
 
-    uint64_t items_per_batch{1'000'000};
-    uint64_t recovery_thread_count{8};
+    uint64_t items_per_batch{1'000'000};  // PER DATABASE
+    uint64_t recovery_thread_count{8};    // PER INSTANCE SYSTEM FLAG
 
     // deprecated
-    bool allow_parallel_index_creation{false};
+    bool allow_parallel_index_creation{false};  // KILL
 
-    bool allow_parallel_schema_creation{false};
+    bool allow_parallel_schema_creation{false};  // PER DATABASE
     friend bool operator==(const Durability &lrh, const Durability &rhs) = default;
   } durability;
 
   struct Transaction {
     IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION};
     friend bool operator==(const Transaction &lrh, const Transaction &rhs) = default;
-  } transaction;
+  } transaction;  // PER DATABASE
 
   struct DiskConfig {
     std::filesystem::path main_storage_directory{"storage/rocksdb_main_storage"};
@@ -89,9 +120,9 @@ struct Config {
     friend bool operator==(const DiskConfig &lrh, const DiskConfig &rhs) = default;
   } disk;
 
-  std::string name;
-  bool force_on_disk{false};
-  StorageMode storage_mode{StorageMode::IN_MEMORY_TRANSACTIONAL};
+  SalientConfig salient;
+
+  bool force_on_disk{false};  // TODO: cleanup.... remove + make the default storage_mode ON_DISK_TRANSACTIONAL if true
 
   friend bool operator==(const Config &lrh, const Config &rhs) = default;
 };
diff --git a/src/storage/v2/database_access.hpp b/src/storage/v2/database_access.hpp
new file mode 100644
index 000000000..de7a1d7d4
--- /dev/null
+++ b/src/storage/v2/database_access.hpp
@@ -0,0 +1,25 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <any>
+
+namespace memgraph::storage {
+
+/**
+ * @brief We need to protect the database using a DatabaseAccess, and we need to keep the replication/storage/dbms
+ * untied. To achieve that we are using std::any, but beware to pass in the correct type using DatabaseAccess =
+ * memgraph::utils::Gatekeeper<memgraph::dbms::Database>::Accessor;
+ */
+using DatabaseAccessProtector = std::any;
+
+}  // namespace memgraph::storage
diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp
index 3f0ab9572..f3c3aa0f4 100644
--- a/src/storage/v2/disk/storage.cpp
+++ b/src/storage/v2/disk/storage.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -288,7 +288,8 @@ DiskStorage::~DiskStorage() {
 
 DiskStorage::DiskAccessor::DiskAccessor(auto tag, DiskStorage *storage, IsolationLevel isolation_level,
                                         StorageMode storage_mode)
-    : Accessor(tag, storage, isolation_level, storage_mode) {
+    : Accessor(tag, storage, isolation_level, storage_mode,
+               memgraph::replication_coordination_glue::ReplicationRole::MAIN) {
   rocksdb::WriteOptions write_options;
   auto txOptions = rocksdb::TransactionOptions{.set_snapshot = true};
   transaction_.disk_transaction_ = storage->kvstore_->db_->BeginTransaction(write_options, txOptions);
@@ -837,10 +838,11 @@ StorageInfo DiskStorage::GetBaseInfo(bool /* unused */) {
   return info;
 }
 
-StorageInfo DiskStorage::GetInfo(bool force_dir) {
+StorageInfo DiskStorage::GetInfo(bool force_dir,
+                                 memgraph::replication_coordination_glue::ReplicationRole replication_role) {
   StorageInfo info = GetBaseInfo(force_dir);
   {
-    auto access = Access(std::nullopt);
+    auto access = Access(replication_role);
     const auto &lbl = access->ListAllIndices();
     info.label_indices = lbl.label.size();
     info.label_property_indices = lbl.label_property.size();
@@ -951,7 +953,7 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from,
   EdgeRef edge(gid);
   bool edge_import_mode_active = disk_storage->edge_import_status_ == EdgeImportMode::ACTIVE;
 
-  if (storage_->config_.items.properties_on_edges) {
+  if (storage_->config_.salient.items.properties_on_edges) {
     auto acc = edge_import_mode_active ? disk_storage->edge_import_mode_cache_->AccessToEdges()
                                        : transaction_.edges_->access();
     auto *delta = CreateDeleteObjectDelta(&transaction_);
@@ -975,7 +977,7 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from,
   transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
   transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
 
-  if (storage_->config_.items.enable_schema_metadata) {
+  if (storage_->config_.salient.items.enable_schema_metadata) {
     storage_->stored_edge_types_.try_insert(edge_type);
   }
   storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
@@ -1283,7 +1285,7 @@ bool DiskStorage::DeleteEdgeFromConnectivityIndex(Transaction *transaction, cons
     const auto src_vertex_gid = modified_edge.second.src_vertex_gid.ToString();
     const auto dst_vertex_gid = modified_edge.second.dest_vertex_gid.ToString();
 
-    if (!config_.items.properties_on_edges) {
+    if (!config_.salient.items.properties_on_edges) {
       /// If the object was created then flush it, otherwise since properties on edges are false
       /// edge wasn't modified for sure.
       if (root_action == Delta::Action::DELETE_OBJECT &&
@@ -1400,7 +1402,7 @@ std::optional<EdgeAccessor> DiskStorage::CreateEdgeFromDisk(const VertexAccessor
   }
 
   EdgeRef edge(gid);
-  if (config_.items.properties_on_edges) {
+  if (config_.salient.items.properties_on_edges) {
     auto acc = edge_import_mode_active ? edge_import_mode_cache_->AccessToEdges() : transaction->edges_->access();
     auto *delta = CreateDeleteDeserializedObjectDelta(transaction, old_disk_key, std::move(read_ts));
     auto [it, inserted] = acc.insert(Edge(gid, delta));
@@ -1458,7 +1460,8 @@ std::vector<EdgeAccessor> DiskStorage::OutEdges(const VertexAccessor *src_vertex
     if (!edge_types.empty() && !utils::Contains(edge_types, edge_type_id)) continue;
 
     auto edge_gid = Gid::FromString(edge_gid_str);
-    auto properties_str = config_.items.properties_on_edges ? utils::GetPropertiesFromEdgeValue(edge_val_str) : "";
+    auto properties_str =
+        config_.salient.items.properties_on_edges ? utils::GetPropertiesFromEdgeValue(edge_val_str) : "";
 
     const auto edge = std::invoke([this, destination, &edge_val_str, transaction, view, src_vertex, edge_type_id,
                                    edge_gid, &properties_str, &edge_gid_str]() {
@@ -1599,7 +1602,7 @@ DiskStorage::CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label,
 
 // NOLINTNEXTLINE(google-default-arguments)
 utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Commit(
-    const std::optional<uint64_t> desired_commit_timestamp, bool /*is_main*/) {
+    CommitReplArgs reparg, DatabaseAccessProtector /*db_acc*/) {
   MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
   MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
 
@@ -1610,7 +1613,7 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co
     // This is usually done by the MVCC, but it does not handle the metadata deltas
     transaction_.EnsureCommitTimestampExists();
     std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
-    commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp));
+    commit_timestamp_.emplace(disk_storage->CommitTimestamp(reparg.desired_commit_timestamp));
     transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release);
 
     for (const auto &md_delta : transaction_.md_deltas) {
@@ -1686,7 +1689,7 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co
               }))) {
   } else {
     std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
-    commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp));
+    commit_timestamp_.emplace(disk_storage->CommitTimestamp(reparg.desired_commit_timestamp));
     transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release);
 
     if (edge_import_mode_active) {
@@ -2005,7 +2008,8 @@ UniqueConstraints::DeletionStatus DiskStorage::DiskAccessor::DropUniqueConstrain
   return UniqueConstraints::DeletionStatus::SUCCESS;
 }
 
-Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool /*is_main*/) {
+Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode,
+                                           memgraph::replication_coordination_glue::ReplicationRole /*is_main*/) {
   /// We acquire the transaction engine lock here because we access (and
   /// modify) the transaction engine variables (`transaction_id` and
   /// `timestamp`) below.
@@ -2030,8 +2034,9 @@ uint64_t DiskStorage::CommitTimestamp(const std::optional<uint64_t> desired_comm
   return *desired_commit_timestamp;
 }
 
-std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLevel> override_isolation_level,
-                                                       bool /*is_main*/) {
+std::unique_ptr<Storage::Accessor> DiskStorage::Access(
+    memgraph::replication_coordination_glue::ReplicationRole /*replication_role*/,
+    std::optional<IsolationLevel> override_isolation_level) {
   auto isolation_level = override_isolation_level.value_or(isolation_level_);
   if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
     throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");
@@ -2039,8 +2044,9 @@ std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLe
   return std::unique_ptr<DiskAccessor>(
       new DiskAccessor{Storage::Accessor::shared_access, this, isolation_level, storage_mode_});
 }
-std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
-                                                             bool /*is_main*/) {
+std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(
+    memgraph::replication_coordination_glue::ReplicationRole /*replication_role*/,
+    std::optional<IsolationLevel> override_isolation_level) {
   auto isolation_level = override_isolation_level.value_or(isolation_level_);
   if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
     throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");
diff --git a/src/storage/v2/disk/storage.hpp b/src/storage/v2/disk/storage.hpp
index 8640462de..293e102b1 100644
--- a/src/storage/v2/disk/storage.hpp
+++ b/src/storage/v2/disk/storage.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -145,8 +145,8 @@ class DiskStorage final : public Storage {
     ConstraintsInfo ListAllConstraints() const override;
 
     // NOLINTNEXTLINE(google-default-arguments)
-    utils::BasicResult<StorageManipulationError, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {},
-                                                              bool is_main = true) override;
+    utils::BasicResult<StorageManipulationError, void> Commit(CommitReplArgs reparg = {},
+                                                              DatabaseAccessProtector db_acc = {}) override;
 
     void UpdateObjectsCountOnAbort();
 
@@ -176,12 +176,12 @@ class DiskStorage final : public Storage {
   };
 
   using Storage::Access;
-  std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level,
-                                            bool is_main) override;
+  std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role,
+                                   std::optional<IsolationLevel> override_isolation_level) override;
 
   using Storage::UniqueAccess;
-  std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
-                                                  bool is_main) override;
+  std::unique_ptr<Accessor> UniqueAccess(memgraph::replication_coordination_glue::ReplicationRole replication_role,
+                                         std::optional<IsolationLevel> override_isolation_level) override;
 
   /// Flushing methods
   [[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushIndexCache(Transaction *transaction);
@@ -284,8 +284,8 @@ class DiskStorage final : public Storage {
 
   RocksDBStorage *GetRocksDBStorage() const { return kvstore_.get(); }
 
-  using Storage::CreateTransaction;
-  Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) override;
+  Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode,
+                                memgraph::replication_coordination_glue::ReplicationRole replication_role) override;
 
   void SetEdgeImportMode(EdgeImportMode edge_import_status);
 
@@ -308,7 +308,8 @@ class DiskStorage final : public Storage {
                                                                                           PropertyId property);
 
   StorageInfo GetBaseInfo(bool force_directory) override;
-  StorageInfo GetInfo(bool force_directory) override;
+  StorageInfo GetInfo(bool force_directory,
+                      memgraph::replication_coordination_glue::ReplicationRole replication_role) override;
 
   void FreeMemory(std::unique_lock<utils::ResourceLock> /*lock*/) override {}
 
diff --git a/src/storage/v2/durability/durability.cpp b/src/storage/v2/durability/durability.cpp
index 6a89b7b5a..92c4d11e8 100644
--- a/src/storage/v2/durability/durability.cpp
+++ b/src/storage/v2/durability/durability.cpp
@@ -428,7 +428,7 @@ std::optional<RecoveryInfo> Recovery::RecoverData(std::string *uuid, Replication
       }
       try {
         auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper,
-                            edge_count, config.items);
+                            edge_count, config.salient.items);
         recovery_info.next_vertex_id = std::max(recovery_info.next_vertex_id, info.next_vertex_id);
         recovery_info.next_edge_id = std::max(recovery_info.next_edge_id, info.next_edge_id);
         recovery_info.next_timestamp = std::max(recovery_info.next_timestamp, info.next_timestamp);
diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp
index 52872222b..0d434fadf 100644
--- a/src/storage/v2/durability/snapshot.cpp
+++ b/src/storage/v2/durability/snapshot.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -222,7 +222,7 @@ std::vector<BatchInfo> ReadBatchInfos(Decoder &snapshot) {
 
 template <typename TFunc>
 void LoadPartialEdges(const std::filesystem::path &path, utils::SkipList<Edge> &edges, const uint64_t from_offset,
-                      const uint64_t edges_count, const Config::Items items, TFunc get_property_from_id) {
+                      const uint64_t edges_count, const SalientConfig::Items items, TFunc get_property_from_id) {
   Decoder snapshot;
   snapshot.Initialize(path, kSnapshotMagic);
 
@@ -420,7 +420,7 @@ template <typename TEdgeTypeFromIdFunc>
 LoadPartialConnectivityResult LoadPartialConnectivity(const std::filesystem::path &path,
                                                       utils::SkipList<Vertex> &vertices, utils::SkipList<Edge> &edges,
                                                       const uint64_t from_offset, const uint64_t vertices_count,
-                                                      const Config::Items items, const bool snapshot_has_edges,
+                                                      const SalientConfig::Items items, const bool snapshot_has_edges,
                                                       TEdgeTypeFromIdFunc get_edge_type_from_id) {
   Decoder snapshot;
   snapshot.Initialize(path, kSnapshotMagic);
@@ -621,7 +621,7 @@ RecoveredSnapshot LoadSnapshotVersion14(const std::filesystem::path &path, utils
                                         utils::SkipList<Edge> *edges,
                                         std::deque<std::pair<std::string, uint64_t>> *epoch_history,
                                         NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count,
-                                        Config::Items items) {
+                                        SalientConfig::Items items) {
   RecoveryInfo ret;
   RecoveredIndicesAndConstraints indices_constraints;
 
@@ -1177,8 +1177,8 @@ RecoveredSnapshot LoadSnapshotVersion15(const std::filesystem::path &path, utils
 
       RecoverOnMultipleThreads(
           config.durability.recovery_thread_count,
-          [path, edges, items = config.items, &get_property_from_id](const size_t /*batch_index*/,
-                                                                     const BatchInfo &batch) {
+          [path, edges, items = config.salient.items, &get_property_from_id](const size_t /*batch_index*/,
+                                                                             const BatchInfo &batch) {
             LoadPartialEdges(path, *edges, batch.offset, batch.count, items, get_property_from_id);
           },
           edge_batches);
@@ -1218,7 +1218,7 @@ RecoveredSnapshot LoadSnapshotVersion15(const std::filesystem::path &path, utils
 
     RecoverOnMultipleThreads(
         config.durability.recovery_thread_count,
-        [path, vertices, edges, edge_count, items = config.items, snapshot_has_edges, &get_edge_type_from_id,
+        [path, vertices, edges, edge_count, items = config.salient.items, snapshot_has_edges, &get_edge_type_from_id,
          &highest_edge_gid, &recovery_info](const size_t batch_index, const BatchInfo &batch) {
           const auto result = LoadPartialConnectivity(path, *vertices, *edges, batch.offset, batch.count, items,
                                                       snapshot_has_edges, get_edge_type_from_id);
@@ -1391,7 +1391,8 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
 
   if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version));
   if (*version == 14U) {
-    return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config.items);
+    return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count,
+                                 config.salient.items);
   }
   if (*version == 15U) {
     return LoadSnapshotVersion15(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config);
@@ -1470,8 +1471,8 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
 
       RecoverOnMultipleThreads(
           config.durability.recovery_thread_count,
-          [path, edges, items = config.items, &get_property_from_id](const size_t /*batch_index*/,
-                                                                     const BatchInfo &batch) {
+          [path, edges, items = config.salient.items, &get_property_from_id](const size_t /*batch_index*/,
+                                                                             const BatchInfo &batch) {
             LoadPartialEdges(path, *edges, batch.offset, batch.count, items, get_property_from_id);
           },
           edge_batches);
@@ -1511,7 +1512,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
 
     RecoverOnMultipleThreads(
         config.durability.recovery_thread_count,
-        [path, vertices, edges, edge_count, items = config.items, snapshot_has_edges, &get_edge_type_from_id,
+        [path, vertices, edges, edge_count, items = config.salient.items, snapshot_has_edges, &get_edge_type_from_id,
          &highest_edge_gid, &recovery_info](const size_t batch_index, const BatchInfo &batch) {
           const auto result = LoadPartialConnectivity(path, *vertices, *edges, batch.offset, batch.count, items,
                                                       snapshot_has_edges, get_edge_type_from_id);
@@ -1868,7 +1869,7 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files
   auto items_in_current_batch{0UL};
   auto batch_start_offset{0UL};
   // Store all edges.
-  if (storage->config_.items.properties_on_edges) {
+  if (storage->config_.salient.items.properties_on_edges) {
     offset_edges = snapshot.GetPosition();
     batch_start_offset = offset_edges;
     auto acc = edges->access();
@@ -1982,18 +1983,34 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files
           snapshot.WritePropertyValue(item.second);
         }
         const auto &in_edges = maybe_in_edges.GetValue().edges;
-        snapshot.WriteUint(in_edges.size());
-        for (const auto &item : in_edges) {
-          snapshot.WriteUint(item.Gid().AsUint());
-          snapshot.WriteUint(item.FromVertex().Gid().AsUint());
-          write_mapping(item.EdgeType());
-        }
         const auto &out_edges = maybe_out_edges.GetValue().edges;
-        snapshot.WriteUint(out_edges.size());
-        for (const auto &item : out_edges) {
-          snapshot.WriteUint(item.Gid().AsUint());
-          snapshot.WriteUint(item.ToVertex().Gid().AsUint());
-          write_mapping(item.EdgeType());
+
+        if (storage->config_.salient.items.properties_on_edges) {
+          snapshot.WriteUint(in_edges.size());
+          for (const auto &item : in_edges) {
+            snapshot.WriteUint(item.GidPropertiesOnEdges().AsUint());
+            snapshot.WriteUint(item.FromVertex().Gid().AsUint());
+            write_mapping(item.EdgeType());
+          }
+          snapshot.WriteUint(out_edges.size());
+          for (const auto &item : out_edges) {
+            snapshot.WriteUint(item.GidPropertiesOnEdges().AsUint());
+            snapshot.WriteUint(item.ToVertex().Gid().AsUint());
+            write_mapping(item.EdgeType());
+          }
+        } else {
+          snapshot.WriteUint(in_edges.size());
+          for (const auto &item : in_edges) {
+            snapshot.WriteUint(item.GidNoPropertiesOnEdges().AsUint());
+            snapshot.WriteUint(item.FromVertex().Gid().AsUint());
+            write_mapping(item.EdgeType());
+          }
+          snapshot.WriteUint(out_edges.size());
+          for (const auto &item : out_edges) {
+            snapshot.WriteUint(item.GidNoPropertiesOnEdges().AsUint());
+            snapshot.WriteUint(item.ToVertex().Gid().AsUint());
+            write_mapping(item.EdgeType());
+          }
         }
       }
 
diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp
index 8c28a6b6d..e808f01a3 100644
--- a/src/storage/v2/durability/wal.cpp
+++ b/src/storage/v2/durability/wal.cpp
@@ -548,7 +548,7 @@ WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder) {
   return delta.type;
 }
 
-void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta,
+void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, SalientConfig::Items items, const Delta &delta,
                  const Vertex &vertex, uint64_t timestamp) {
   // When converting a Delta to a WAL delta the logic is inverted. That is
   // because the Delta's represent undo actions and we want to store redo
@@ -709,7 +709,7 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage
 RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints,
                      const std::optional<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices,
                      utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count,
-                     Config::Items items) {
+                     SalientConfig::Items items) {
   spdlog::info("Trying to load WAL file {}.", path);
   RecoveryInfo ret;
 
@@ -983,8 +983,8 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst
 }
 
 WalFile::WalFile(const std::filesystem::path &wal_directory, const std::string_view uuid,
-                 const std::string_view epoch_id, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num,
-                 utils::FileRetainer *file_retainer)
+                 const std::string_view epoch_id, SalientConfig::Items items, NameIdMapper *name_id_mapper,
+                 uint64_t seq_num, utils::FileRetainer *file_retainer)
     : items_(items),
       name_id_mapper_(name_id_mapper),
       path_(wal_directory / MakeWalName()),
@@ -1026,7 +1026,7 @@ WalFile::WalFile(const std::filesystem::path &wal_directory, const std::string_v
   wal_.Sync();
 }
 
-WalFile::WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper,
+WalFile::WalFile(std::filesystem::path current_wal_path, SalientConfig::Items items, NameIdMapper *name_id_mapper,
                  uint64_t seq_num, uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count,
                  utils::FileRetainer *file_retainer)
     : items_(items),
diff --git a/src/storage/v2/durability/wal.hpp b/src/storage/v2/durability/wal.hpp
index 8f6492ac7..20d88b040 100644
--- a/src/storage/v2/durability/wal.hpp
+++ b/src/storage/v2/durability/wal.hpp
@@ -193,7 +193,7 @@ WalDeltaData ReadWalDeltaData(BaseDecoder *decoder);
 WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder);
 
 /// Function used to encode a `Delta` that originated from a `Vertex`.
-void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta,
+void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, SalientConfig::Items items, const Delta &delta,
                  const Vertex &vertex, uint64_t timestamp);
 
 /// Function used to encode a `Delta` that originated from an `Edge`.
@@ -213,15 +213,17 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage
 RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints,
                      std::optional<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices,
                      utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count,
-                     Config::Items items);
+                     SalientConfig::Items items);
 
 /// WalFile class used to append deltas and operations to the WAL file.
 class WalFile {
  public:
   WalFile(const std::filesystem::path &wal_directory, std::string_view uuid, std::string_view epoch_id,
-          Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, utils::FileRetainer *file_retainer);
-  WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num,
-          uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count, utils::FileRetainer *file_retainer);
+          SalientConfig::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num,
+          utils::FileRetainer *file_retainer);
+  WalFile(std::filesystem::path current_wal_path, SalientConfig::Items items, NameIdMapper *name_id_mapper,
+          uint64_t seq_num, uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count,
+          utils::FileRetainer *file_retainer);
 
   WalFile(const WalFile &) = delete;
   WalFile(WalFile &&) = delete;
@@ -268,7 +270,7 @@ class WalFile {
  private:
   void UpdateStats(uint64_t timestamp);
 
-  Config::Items items_;
+  SalientConfig::Items items_;
   NameIdMapper *name_id_mapper_;
   Encoder wal_;
   std::filesystem::path path_;
diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp
index 7e7166117..3ab2e3d79 100644
--- a/src/storage/v2/edge_accessor.cpp
+++ b/src/storage/v2/edge_accessor.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -21,12 +21,13 @@
 #include "storage/v2/result.hpp"
 #include "storage/v2/storage.hpp"
 #include "storage/v2/vertex_accessor.hpp"
+#include "utils/atomic_memory_block.hpp"
 #include "utils/memory_tracker.hpp"
 
 namespace memgraph::storage {
 
 bool EdgeAccessor::IsDeleted() const {
-  if (!storage_->config_.items.properties_on_edges) {
+  if (!storage_->config_.salient.items.properties_on_edges) {
     return false;
   }
   return edge_.ptr->deleted;
@@ -37,7 +38,7 @@ bool EdgeAccessor::IsVisible(const View view) const {
   bool deleted = true;
   // When edges don't have properties, their isolation level is still dictated by MVCC ->
   // iterate over the deltas of the from_vertex_ and see which deltas can be applied on edges.
-  if (!storage_->config_.items.properties_on_edges) {
+  if (!storage_->config_.salient.items.properties_on_edges) {
     Delta *delta = nullptr;
     {
       auto guard = std::shared_lock{from_vertex_->lock};
@@ -119,36 +120,40 @@ VertexAccessor EdgeAccessor::DeletedEdgeToVertex() const {
 
 Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
   utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
-  if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
+  if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
 
   auto guard = std::unique_lock{edge_.ptr->lock};
 
   if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
 
   if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
-
-  auto current_value = edge_.ptr->properties.GetProperty(property);
-  // We could skip setting the value if the previous one is the same to the new
-  // one. This would save some memory as a delta would not be created as well as
-  // avoid copying the value. The reason we are not doing that is because the
-  // current code always follows the logical pattern of "create a delta" and
-  // "modify in-place". Additionally, the created delta will make other
-  // transactions get a SERIALIZATION_ERROR.
-
-  CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, current_value);
-  edge_.ptr->properties.SetProperty(property, value);
+  using ReturnType = decltype(edge_.ptr->properties.GetProperty(property));
+  std::optional<ReturnType> current_value;
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [&current_value, &property, &value, transaction = transaction_, edge = edge_]() {
+        current_value.emplace(edge.ptr->properties.GetProperty(property));
+        // We could skip setting the value if the previous one is the same to the new
+        // one. This would save some memory as a delta would not be created as well as
+        // avoid copying the value. The reason we are not doing that is because the
+        // current code always follows the logical pattern of "create a delta" and
+        // "modify in-place". Additionally, the created delta will make other
+        // transactions get a SERIALIZATION_ERROR.
+        CreateAndLinkDelta(transaction, edge.ptr, Delta::SetPropertyTag(), property, *current_value);
+        edge.ptr->properties.SetProperty(property, value);
+      }};
+  std::invoke(atomic_memory_block);
 
   if (transaction_->IsDiskStorage()) {
     ModifiedEdgeInfo modified_edge(Delta::Action::SET_PROPERTY, from_vertex_->gid, to_vertex_->gid, edge_type_, edge_);
     transaction_->AddModifiedEdge(Gid(), modified_edge);
   }
 
-  return std::move(current_value);
+  return std::move(*current_value);
 }
 
 Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
   utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
-  if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
+  if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
 
   auto guard = std::unique_lock{edge_.ptr->lock};
 
@@ -157,9 +162,12 @@ Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, st
   if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
 
   if (!edge_.ptr->properties.InitProperties(properties)) return false;
-  for (const auto &[property, _] : properties) {
-    CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue());
-  }
+  utils::AtomicMemoryBlock atomic_memory_block{[&properties, transaction_ = transaction_, edge_ = edge_]() {
+    for (const auto &[property, _] : properties) {
+      CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue());
+    }
+  }};
+  std::invoke(atomic_memory_block);
 
   return true;
 }
@@ -167,7 +175,7 @@ Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, st
 Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> EdgeAccessor::UpdateProperties(
     std::map<storage::PropertyId, storage::PropertyValue> &properties) const {
   utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
-  if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
+  if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
 
   auto guard = std::unique_lock{edge_.ptr->lock};
 
@@ -175,17 +183,22 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> EdgeAc
 
   if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
 
-  auto id_old_new_change = edge_.ptr->properties.UpdateProperties(properties);
+  using ReturnType = decltype(edge_.ptr->properties.UpdateProperties(properties));
+  std::optional<ReturnType> id_old_new_change;
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [transaction_ = transaction_, edge_ = edge_, &properties, &id_old_new_change]() {
+        id_old_new_change.emplace(edge_.ptr->properties.UpdateProperties(properties));
+        for (auto &[property, old_value, new_value] : *id_old_new_change) {
+          CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, std::move(old_value));
+        }
+      }};
+  std::invoke(atomic_memory_block);
 
-  for (auto &[property, old_value, new_value] : id_old_new_change) {
-    CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, std::move(old_value));
-  }
-
-  return id_old_new_change;
+  return id_old_new_change.has_value() ? std::move(id_old_new_change.value()) : ReturnType{};
 }
 
 Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
-  if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
+  if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED;
 
   auto guard = std::unique_lock{edge_.ptr->lock};
 
@@ -193,33 +206,38 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
 
   if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
 
-  auto properties = edge_.ptr->properties.Properties();
-  for (const auto &property : properties) {
-    CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second);
-  }
+  using ReturnType = decltype(edge_.ptr->properties.Properties());
+  std::optional<ReturnType> properties;
+  utils::AtomicMemoryBlock atomic_memory_block{[&properties, transaction_ = transaction_, edge_ = edge_]() {
+    properties.emplace(edge_.ptr->properties.Properties());
+    for (const auto &property : *properties) {
+      CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second);
+    }
 
-  edge_.ptr->properties.ClearProperties();
+    edge_.ptr->properties.ClearProperties();
+  }};
+  std::invoke(atomic_memory_block);
 
-  return std::move(properties);
+  return properties.has_value() ? std::move(properties.value()) : ReturnType{};
 }
 
 Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) const {
-  if (!storage_->config_.items.properties_on_edges) return PropertyValue();
+  if (!storage_->config_.salient.items.properties_on_edges) return PropertyValue();
   bool exists = true;
   bool deleted = false;
-  PropertyValue value;
+  std::optional<PropertyValue> value;
   Delta *delta = nullptr;
   {
     auto guard = std::shared_lock{edge_.ptr->lock};
     deleted = edge_.ptr->deleted;
-    value = edge_.ptr->properties.GetProperty(property);
+    value.emplace(edge_.ptr->properties.GetProperty(property));
     delta = edge_.ptr->delta;
   }
   ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) {
     switch (delta.action) {
       case Delta::Action::SET_PROPERTY: {
         if (delta.property.key == property) {
-          value = delta.property.value;
+          *value = delta.property.value;
         }
         break;
       }
@@ -243,11 +261,11 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view)
   });
   if (!exists) return Error::NONEXISTENT_OBJECT;
   if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
-  return std::move(value);
+  return *std::move(value);
 }
 
 Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) const {
-  if (!storage_->config_.items.properties_on_edges) return std::map<PropertyId, PropertyValue>{};
+  if (!storage_->config_.salient.items.properties_on_edges) return std::map<PropertyId, PropertyValue>{};
   bool exists = true;
   bool deleted = false;
   std::map<PropertyId, PropertyValue> properties;
@@ -299,7 +317,7 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view)
 }
 
 Gid EdgeAccessor::Gid() const noexcept {
-  if (storage_->config_.items.properties_on_edges) {
+  if (storage_->config_.salient.items.properties_on_edges) {
     return edge_.ptr->gid;
   }
   return edge_.gid;
diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp
index a1c52d0a5..83a3e549d 100644
--- a/src/storage/v2/edge_accessor.hpp
+++ b/src/storage/v2/edge_accessor.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -85,6 +85,8 @@ class EdgeAccessor final {
   /// @throw std::bad_alloc
   Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
 
+  auto GidPropertiesOnEdges() const -> Gid { return edge_.ptr->gid; }
+  auto GidNoPropertiesOnEdges() const -> Gid { return edge_.gid; }
   Gid Gid() const noexcept;
 
   bool IsCycle() const { return from_vertex_ == to_vertex_; }
diff --git a/src/storage/v2/indices/indices.cpp b/src/storage/v2/indices/indices.cpp
index e0b194ad4..c86ec8442 100644
--- a/src/storage/v2/indices/indices.cpp
+++ b/src/storage/v2/indices/indices.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -32,10 +32,10 @@ void Indices::AbortEntries(LabelId label, std::span<std::pair<PropertyValue, Ver
       ->AbortEntries(label, vertices, exact_start_timestamp);
 }
 
-void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) const {
-  static_cast<InMemoryLabelIndex *>(label_index_.get())->RemoveObsoleteEntries(oldest_active_start_timestamp);
+void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) const {
+  static_cast<InMemoryLabelIndex *>(label_index_.get())->RemoveObsoleteEntries(oldest_active_start_timestamp, token);
   static_cast<InMemoryLabelPropertyIndex *>(label_property_index_.get())
-      ->RemoveObsoleteEntries(oldest_active_start_timestamp);
+      ->RemoveObsoleteEntries(oldest_active_start_timestamp, std::move(token));
 }
 
 void Indices::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) const {
diff --git a/src/storage/v2/indices/indices.hpp b/src/storage/v2/indices/indices.hpp
index 33bd429e6..d95187bbb 100644
--- a/src/storage/v2/indices/indices.hpp
+++ b/src/storage/v2/indices/indices.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -33,7 +33,7 @@ struct Indices {
   /// This function should be called from garbage collection to clean-up the
   /// index.
   /// TODO: unused in disk indices
-  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) const;
+  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) const;
 
   /// Surgical removal of entries that was inserted this transaction
   /// TODO: unused in disk indices
diff --git a/src/storage/v2/indices/indices_utils.hpp b/src/storage/v2/indices/indices_utils.hpp
index 59b492ba3..054609188 100644
--- a/src/storage/v2/indices/indices_utils.hpp
+++ b/src/storage/v2/indices/indices_utils.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -21,11 +21,18 @@
 
 namespace memgraph::storage {
 
+namespace {
+
+template <Delta::Action... actions>
+struct ActionSet {
+  constexpr bool contains(Delta::Action action) const { return ((action == actions) || ...); }
+};
+
 /// Traverses deltas visible from transaction with start timestamp greater than
 /// the provided timestamp, and calls the provided callback function for each
 /// delta. If the callback ever returns true, traversal is stopped and the
 /// function returns true. Otherwise, the function returns false.
-template <typename TCallback>
+template <ActionSet interesting, typename TCallback>
 inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, const TCallback &predicate) {
   while (delta != nullptr) {
     const auto ts = delta->timestamp->load(std::memory_order_acquire);
@@ -33,7 +40,7 @@ inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta,
     if (ts < timestamp) {
       break;
     }
-    if (predicate(*delta)) {
+    if (interesting.contains(delta->action) && predicate(*delta)) {
       return true;
     }
     // Move to the next delta.
@@ -42,6 +49,8 @@ inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta,
   return false;
 }
 
+}  // namespace
+
 /// Helper function for label index garbage collection. Returns true if there's
 /// a reachable version of the vertex that has the given label.
 inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp) {
@@ -57,7 +66,10 @@ inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t tim
   if (!deleted && has_label) {
     return true;
   }
-  return AnyVersionSatisfiesPredicate(timestamp, delta, [&has_label, &deleted, label](const Delta &delta) {
+  constexpr auto interesting =
+      ActionSet<Delta::Action::ADD_LABEL, Delta::Action::REMOVE_LABEL, Delta::Action::RECREATE_OBJECT,
+                Delta::Action::DELETE_DESERIALIZED_OBJECT, Delta::Action::DELETE_OBJECT>{};
+  return AnyVersionSatisfiesPredicate<interesting>(timestamp, delta, [&has_label, &deleted, label](const Delta &delta) {
     switch (delta.action) {
       case Delta::Action::ADD_LABEL:
         if (delta.label == label) {
@@ -98,10 +110,10 @@ inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t tim
 /// property value.
 inline bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId key, const PropertyValue &value,
                                        uint64_t timestamp) {
-  bool has_label{false};
-  bool current_value_equal_to_value{value.IsNull()};
-  bool deleted{false};
-  const Delta *delta = nullptr;
+  Delta const *delta;
+  bool deleted;
+  bool has_label;
+  bool current_value_equal_to_value;
   {
     auto guard = std::shared_lock{vertex.lock};
     delta = vertex.delta;
@@ -116,7 +128,10 @@ inline bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, Prop
     return true;
   }
 
-  return AnyVersionSatisfiesPredicate(
+  constexpr auto interesting = ActionSet<Delta::Action::ADD_LABEL, Delta::Action::REMOVE_LABEL,
+                                         Delta::Action::SET_PROPERTY, Delta::Action::RECREATE_OBJECT,
+                                         Delta::Action::DELETE_DESERIALIZED_OBJECT, Delta::Action::DELETE_OBJECT>{};
+  return AnyVersionSatisfiesPredicate<interesting>(
       timestamp, delta, [&has_label, &current_value_equal_to_value, &deleted, label, key, &value](const Delta &delta) {
         switch (delta.action) {
           case Delta::Action::ADD_LABEL:
diff --git a/src/storage/v2/inmemory/label_index.cpp b/src/storage/v2/inmemory/label_index.cpp
index b833c97ff..9ab027308 100644
--- a/src/storage/v2/inmemory/label_index.cpp
+++ b/src/storage/v2/inmemory/label_index.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,6 +16,7 @@
 #include "storage/v2/constraints/constraints.hpp"
 #include "storage/v2/indices/indices_utils.hpp"
 #include "storage/v2/inmemory/storage.hpp"
+#include "utils/counter.hpp"
 
 namespace memgraph::storage {
 
@@ -79,10 +80,18 @@ std::vector<LabelId> InMemoryLabelIndex::ListIndices() const {
   return ret;
 }
 
-void InMemoryLabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
+void InMemoryLabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) {
+  auto maybe_stop = utils::ResettableCounter<2048>();
+
   for (auto &label_storage : index_) {
+    // before starting index, check if stop_requested
+    if (token.stop_requested()) return;
+
     auto vertices_acc = label_storage.second.access();
     for (auto it = vertices_acc.begin(); it != vertices_acc.end();) {
+      // Hot loop, don't check stop_requested every time
+      if (maybe_stop() && token.stop_requested()) return;
+
       auto next_it = it;
       ++next_it;
 
diff --git a/src/storage/v2/inmemory/label_index.hpp b/src/storage/v2/inmemory/label_index.hpp
index 2411f0ba1..5ecac117b 100644
--- a/src/storage/v2/inmemory/label_index.hpp
+++ b/src/storage/v2/inmemory/label_index.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -54,7 +54,7 @@ class InMemoryLabelIndex : public storage::LabelIndex {
 
   std::vector<LabelId> ListIndices() const override;
 
-  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
+  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token);
 
   /// Surgical removal of entries that was inserted this transaction
   void AbortEntries(LabelId labelId, std::span<Vertex *const> vertices, uint64_t exact_start_timestamp);
diff --git a/src/storage/v2/inmemory/label_property_index.cpp b/src/storage/v2/inmemory/label_property_index.cpp
index c8333fb95..59b12a779 100644
--- a/src/storage/v2/inmemory/label_property_index.cpp
+++ b/src/storage/v2/inmemory/label_property_index.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,6 +13,7 @@
 #include "storage/v2/constraints/constraints.hpp"
 #include "storage/v2/indices/indices_utils.hpp"
 #include "storage/v2/inmemory/storage.hpp"
+#include "utils/counter.hpp"
 #include "utils/logging.hpp"
 
 namespace memgraph::storage {
@@ -139,10 +140,18 @@ std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::ListIndi
   return ret;
 }
 
-void InMemoryLabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
+void InMemoryLabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) {
+  auto maybe_stop = utils::ResettableCounter<2048>();
+
   for (auto &[label_property, index] : index_) {
+    // before starting index, check if stop_requested
+    if (token.stop_requested()) return;
+
     auto index_acc = index.access();
     for (auto it = index_acc.begin(); it != index_acc.end();) {
+      // Hot loop, don't check stop_requested every time
+      if (maybe_stop() && token.stop_requested()) return;
+
       auto next_it = it;
       ++next_it;
 
diff --git a/src/storage/v2/inmemory/label_property_index.hpp b/src/storage/v2/inmemory/label_property_index.hpp
index 8bc4148bb..6ca67e1c6 100644
--- a/src/storage/v2/inmemory/label_property_index.hpp
+++ b/src/storage/v2/inmemory/label_property_index.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -60,7 +60,7 @@ class InMemoryLabelPropertyIndex : public storage::LabelPropertyIndex {
 
   std::vector<std::pair<LabelId, PropertyId>> ListIndices() const override;
 
-  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
+  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token);
 
   void AbortEntries(PropertyId property, std::span<std::pair<PropertyValue, Vertex *> const> vertices,
                     uint64_t exact_start_timestamp);
diff --git a/src/storage/v2/inmemory/replication/recovery.cpp b/src/storage/v2/inmemory/replication/recovery.cpp
index 536c7c8fc..d6f2b464c 100644
--- a/src/storage/v2/inmemory/replication/recovery.cpp
+++ b/src/storage/v2/inmemory/replication/recovery.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -44,7 +44,7 @@ class InMemoryCurrentWalHandler {
 
 ////// CurrentWalHandler //////
 InMemoryCurrentWalHandler::InMemoryCurrentWalHandler(InMemoryStorage const *storage, rpc::Client &rpc_client)
-    : stream_(rpc_client.Stream<replication::CurrentWalRpc>(storage->id())) {}
+    : stream_(rpc_client.Stream<replication::CurrentWalRpc>(storage->uuid())) {}
 
 void InMemoryCurrentWalHandler::AppendFilename(const std::string &filename) {
   replication::Encoder encoder(stream_.GetBuilder());
@@ -69,10 +69,10 @@ void InMemoryCurrentWalHandler::AppendBufferData(const uint8_t *buffer, const si
 replication::CurrentWalRes InMemoryCurrentWalHandler::Finalize() { return stream_.AwaitResponse(); }
 
 ////// ReplicationClient Helpers //////
-replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &client,
+replication::WalFilesRes TransferWalFiles(const utils::UUID &uuid, rpc::Client &client,
                                           const std::vector<std::filesystem::path> &wal_files) {
   MG_ASSERT(!wal_files.empty(), "Wal files list is empty!");
-  auto stream = client.Stream<replication::WalFilesRpc>(std::move(db_name), wal_files.size());
+  auto stream = client.Stream<replication::WalFilesRpc>(uuid, wal_files.size());
   replication::Encoder encoder(stream.GetBuilder());
   for (const auto &wal : wal_files) {
     spdlog::debug("Sending wal file: {}", wal);
@@ -81,8 +81,9 @@ replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &clie
   return stream.AwaitResponse();
 }
 
-replication::SnapshotRes TransferSnapshot(std::string db_name, rpc::Client &client, const std::filesystem::path &path) {
-  auto stream = client.Stream<replication::SnapshotRpc>(std::move(db_name));
+replication::SnapshotRes TransferSnapshot(const utils::UUID &uuid, rpc::Client &client,
+                                          const std::filesystem::path &path) {
+  auto stream = client.Stream<replication::SnapshotRpc>(uuid);
   replication::Encoder encoder(stream.GetBuilder());
   encoder.WriteFile(path);
   return stream.AwaitResponse();
diff --git a/src/storage/v2/inmemory/replication/recovery.hpp b/src/storage/v2/inmemory/replication/recovery.hpp
index 2025800ab..730822a62 100644
--- a/src/storage/v2/inmemory/replication/recovery.hpp
+++ b/src/storage/v2/inmemory/replication/recovery.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -19,10 +19,11 @@ class InMemoryStorage;
 
 ////// ReplicationClient Helpers //////
 
-replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &client,
+replication::WalFilesRes TransferWalFiles(const utils::UUID &uuid, rpc::Client &client,
                                           const std::vector<std::filesystem::path> &wal_files);
 
-replication::SnapshotRes TransferSnapshot(std::string db_name, rpc::Client &client, const std::filesystem::path &path);
+replication::SnapshotRes TransferSnapshot(const utils::UUID &uuid, rpc::Client &client,
+                                          const std::filesystem::path &path);
 
 uint64_t ReplicateCurrentWal(const InMemoryStorage *storage, rpc::Client &client, durability::WalFile const &wal_file);
 
diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp
index 08aa896bf..381a67d3f 100644
--- a/src/storage/v2/inmemory/storage.cpp
+++ b/src/storage/v2/inmemory/storage.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -26,6 +26,7 @@
 #include "storage/v2/inmemory/replication/recovery.hpp"
 #include "storage/v2/inmemory/unique_constraints.hpp"
 #include "storage/v2/property_value.hpp"
+#include "utils/atomic_memory_block.hpp"
 #include "utils/resource_lock.hpp"
 #include "utils/stat.hpp"
 
@@ -64,14 +65,14 @@ auto FindEdges(const View view, EdgeTypeId edge_type, const VertexAccessor *from
 
 using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler;
 
-InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
-    : Storage(config, storage_mode),
+InMemoryStorage::InMemoryStorage(Config config)
+    : Storage(config, config.salient.storage_mode),
       recovery_{config.durability.storage_directory / durability::kSnapshotDirectory,
                 config.durability.storage_directory / durability::kWalDirectory},
       lock_file_path_(config.durability.storage_directory / durability::kLockFile),
       uuid_(utils::GenerateUUID()),
       global_locker_(file_retainer_.AddLocker()) {
-  MG_ASSERT(storage_mode != StorageMode::ON_DISK_TRANSACTIONAL,
+  MG_ASSERT(config.salient.storage_mode != StorageMode::ON_DISK_TRANSACTIONAL,
             "Invalid storage mode sent to InMemoryStorage constructor!");
   if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED ||
       config_.durability.snapshot_on_exit || config_.durability.recover_on_startup) {
@@ -144,7 +145,6 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
     gc_runner_.Run("Storage GC", config_.gc.interval, [this] {
       this->FreeMemory(std::unique_lock<utils::ResourceLock>{main_lock_, std::defer_lock});
     });
-    gc_jemalloc_runner_.Run("Jemalloc GC", config_.gc.interval, [] { memory::PurgeUnusedMemory(); });
   }
   if (timestamp_ == kTimestampInitialId) {
     commit_log_.emplace();
@@ -153,12 +153,11 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
   }
 }
 
-InMemoryStorage::InMemoryStorage(Config config) : InMemoryStorage(config, StorageMode::IN_MEMORY_TRANSACTIONAL) {}
-
 InMemoryStorage::~InMemoryStorage() {
+  stop_source.request_stop();
+
   if (config_.gc.type == Config::Gc::Type::PERIODIC) {
     gc_runner_.Stop();
-    gc_jemalloc_runner_.Stop();
   }
   {
     // Stop replication (Stop all clients or stop the REPLICA server)
@@ -178,8 +177,10 @@ InMemoryStorage::~InMemoryStorage() {
 }
 
 InMemoryStorage::InMemoryAccessor::InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
-                                                    StorageMode storage_mode, bool is_main)
-    : Accessor(tag, storage, isolation_level, storage_mode, is_main), config_(storage->config_.items) {}
+                                                    StorageMode storage_mode,
+                                                    memgraph::replication_coordination_glue::ReplicationRole replication_role)
+    : Accessor(tag, storage, isolation_level, storage_mode, replication_role),
+      config_(storage->config_.salient.items) {}
 InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept
     : Accessor(std::move(other)), config_(other.config_) {}
 
@@ -321,7 +322,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso
     if (to_vertex->deleted) return Error::DELETED_OBJECT;
   }
 
-  if (storage_->config_.items.enable_schema_metadata) {
+  if (storage_->config_.salient.items.enable_schema_metadata) {
     storage_->stored_edge_types_.try_insert(edge_type);
   }
   auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
@@ -338,18 +339,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso
       delta->prev.Set(&*it);
     }
   }
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [this, edge, from_vertex = from_vertex, edge_type = edge_type, to_vertex = to_vertex]() {
+        CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge);
+        from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge);
 
-  CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge);
-  from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge);
+        CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge);
+        to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge);
 
-  CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge);
-  to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge);
+        transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
+        transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
 
-  transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
-  transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
+        // Increment edge count.
+        storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
+      }};
 
-  // Increment edge count.
-  storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
+  std::invoke(atomic_memory_block);
 
   return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, storage_, &transaction_);
 }
@@ -406,7 +411,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdgeEx(VertexAcces
     if (to_vertex->deleted) return Error::DELETED_OBJECT;
   }
 
-  if (storage_->config_.items.enable_schema_metadata) {
+  if (storage_->config_.salient.items.enable_schema_metadata) {
     storage_->stored_edge_types_.try_insert(edge_type);
   }
 
@@ -433,18 +438,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdgeEx(VertexAcces
       delta->prev.Set(&*it);
     }
   }
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [this, edge, from_vertex = from_vertex, edge_type = edge_type, to_vertex = to_vertex]() {
+        CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge);
+        from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge);
 
-  CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge);
-  from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge);
+        CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge);
+        to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge);
 
-  CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge);
-  to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge);
+        transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
+        transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
 
-  transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
-  transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
+        // Increment edge count.
+        storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
+      }};
 
-  // Increment edge count.
-  storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
+  std::invoke(atomic_memory_block);
 
   return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, storage_, &transaction_);
 }
@@ -534,18 +543,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetFrom(EdgeAccessor
       return Error::DELETED_OBJECT;
     }
   }
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [this, edge_ref, old_from_vertex, new_from_vertex, edge_type, to_vertex]() {
+        CreateAndLinkDelta(&transaction_, old_from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref);
+        CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, old_from_vertex, edge_ref);
 
-  CreateAndLinkDelta(&transaction_, old_from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref);
-  CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, old_from_vertex, edge_ref);
+        CreateAndLinkDelta(&transaction_, new_from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge_ref);
+        new_from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge_ref);
+        CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, new_from_vertex, edge_ref);
+        to_vertex->in_edges.emplace_back(edge_type, new_from_vertex, edge_ref);
 
-  CreateAndLinkDelta(&transaction_, new_from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge_ref);
-  new_from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge_ref);
-  CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, new_from_vertex, edge_ref);
-  to_vertex->in_edges.emplace_back(edge_type, new_from_vertex, edge_ref);
+        transaction_.manyDeltasCache.Invalidate(new_from_vertex, edge_type, EdgeDirection::OUT);
+        transaction_.manyDeltasCache.Invalidate(old_from_vertex, edge_type, EdgeDirection::OUT);
+        transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
+      }};
 
-  transaction_.manyDeltasCache.Invalidate(new_from_vertex, edge_type, EdgeDirection::OUT);
-  transaction_.manyDeltasCache.Invalidate(old_from_vertex, edge_type, EdgeDirection::OUT);
-  transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN);
+  std::invoke(atomic_memory_block);
 
   return EdgeAccessor(edge_ref, edge_type, new_from_vertex, to_vertex, storage_, &transaction_);
 }
@@ -636,17 +649,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor *
     }
   }
 
-  CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, old_to_vertex, edge_ref);
-  CreateAndLinkDelta(&transaction_, old_to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref);
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [this, edge_ref, old_to_vertex, from_vertex, edge_type, new_to_vertex]() {
+        CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, old_to_vertex, edge_ref);
+        CreateAndLinkDelta(&transaction_, old_to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref);
 
-  CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, new_to_vertex, edge_ref);
-  from_vertex->out_edges.emplace_back(edge_type, new_to_vertex, edge_ref);
-  CreateAndLinkDelta(&transaction_, new_to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge_ref);
-  new_to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge_ref);
+        CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, new_to_vertex, edge_ref);
+        from_vertex->out_edges.emplace_back(edge_type, new_to_vertex, edge_ref);
+        CreateAndLinkDelta(&transaction_, new_to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge_ref);
+        new_to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge_ref);
 
-  transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
-  transaction_.manyDeltasCache.Invalidate(old_to_vertex, edge_type, EdgeDirection::IN);
-  transaction_.manyDeltasCache.Invalidate(new_to_vertex, edge_type, EdgeDirection::IN);
+        transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT);
+        transaction_.manyDeltasCache.Invalidate(old_to_vertex, edge_type, EdgeDirection::IN);
+        transaction_.manyDeltasCache.Invalidate(new_to_vertex, edge_type, EdgeDirection::IN);
+      }};
+
+  std::invoke(atomic_memory_block);
 
   return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_);
 }
@@ -709,24 +727,28 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAcces
 
   MG_ASSERT((op1 && op2), "Invalid database state!");
 
-  // "deleting" old edge
-  CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref);
-  CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref);
+  utils::AtomicMemoryBlock atomic_memory_block{[this, to_vertex, new_edge_type, edge_ref, from_vertex, edge_type]() {
+    // "deleting" old edge
+    CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref);
+    CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref);
 
-  // "adding" new edge
-  CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), new_edge_type, to_vertex, edge_ref);
-  CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), new_edge_type, from_vertex, edge_ref);
+    // "adding" new edge
+    CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), new_edge_type, to_vertex, edge_ref);
+    CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), new_edge_type, from_vertex, edge_ref);
 
-  // edge type is not used while invalidating cache so we can only call it once
-  transaction_.manyDeltasCache.Invalidate(from_vertex, new_edge_type, EdgeDirection::OUT);
-  transaction_.manyDeltasCache.Invalidate(to_vertex, new_edge_type, EdgeDirection::IN);
+    // edge type is not used while invalidating cache so we can only call it once
+    transaction_.manyDeltasCache.Invalidate(from_vertex, new_edge_type, EdgeDirection::OUT);
+    transaction_.manyDeltasCache.Invalidate(to_vertex, new_edge_type, EdgeDirection::IN);
+  }};
+
+  std::invoke(atomic_memory_block);
 
   return EdgeAccessor(edge_ref, new_edge_type, from_vertex, to_vertex, storage_, &transaction_);
 }
 
 // NOLINTNEXTLINE(google-default-arguments)
 utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
-    const std::optional<uint64_t> desired_commit_timestamp, bool is_main) {
+    CommitReplArgs reparg, DatabaseAccessProtector db_acc) {
   MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
   MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
 
@@ -735,47 +757,14 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
   auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
 
   // TODO: duplicated transaction finalisation in md_deltas and deltas processing cases
-  if (!transaction_.md_deltas.empty()) {
-    // This is usually done by the MVCC, but it does not handle the metadata deltas
-    transaction_.EnsureCommitTimestampExists();
-
-    // Save these so we can mark them used in the commit log.
-    uint64_t start_timestamp = transaction_.start_timestamp;
-
-    std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
-    commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp));
-
-    // Write transaction to WAL while holding the engine lock to make sure
-    // that committed transactions are sorted by the commit timestamp in the
-    // WAL files. We supply the new commit timestamp to the function so that
-    // it knows what will be the final commit timestamp. The WAL must be
-    // written before actually committing the transaction (before setting
-    // the commit timestamp) so that no other transaction can see the
-    // modifications before they are written to disk.
-    // Replica can log only the write transaction received from Main
-    // so the Wal files are consistent
-    if (is_main || desired_commit_timestamp.has_value()) {
-      could_replicate_all_sync_replicas =
-          mem_storage->AppendToWalDataDefinition(transaction_, *commit_timestamp_);  // protected by engine_guard
-      // TODO: release lock, and update all deltas to have a local copy of the commit timestamp
-      transaction_.commit_timestamp->store(*commit_timestamp_,
-                                           std::memory_order_release);  // protected by engine_guard
-      // Replica can only update the last commit timestamp with
-      // the commits received from main.
-      if (is_main || desired_commit_timestamp.has_value()) {
-        // Update the last commit timestamp
-        mem_storage->repl_storage_state_.last_commit_timestamp_.store(*commit_timestamp_);  // protected by engine_guard
-      }
-      // Release engine lock because we don't have to hold it anymore
-      engine_guard.unlock();
-
-      mem_storage->commit_log_->MarkFinished(start_timestamp);
-    }
-  } else if (transaction_.deltas.use().empty()) {
+  if (transaction_.deltas.use().empty() && transaction_.md_deltas.empty()) {
     // We don't have to update the commit timestamp here because no one reads
     // it.
     mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp);
   } else {
+    // This is usually done by the MVCC, but it does not handle the metadata deltas
+    transaction_.EnsureCommitTimestampExists();
+
     if (transaction_.constraint_verification_info.NeedsExistenceConstraintVerification()) {
       const auto vertices_to_update =
           transaction_.constraint_verification_info.GetVerticesForExistenceConstraintChecking();
@@ -803,7 +792,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
       std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
       auto *mem_unique_constraints =
           static_cast<InMemoryUniqueConstraints *>(storage_->constraints_.unique_constraints_.get());
-      commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp));
+      commit_timestamp_.emplace(mem_storage->CommitTimestamp(reparg.desired_commit_timestamp));
 
       if (transaction_.constraint_verification_info.NeedsUniqueConstraintVerification()) {
         // Before committing and validating vertices against unique constraints,
@@ -827,6 +816,16 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
       }
 
       if (!unique_constraint_violation) {
+        [[maybe_unused]] bool const is_main_or_replica_write =
+            reparg.IsMain() || reparg.desired_commit_timestamp.has_value();
+
+        // TODO Figure out if we can assert this
+        // DMG_ASSERT(is_main_or_replica_write, "Should only get here on writes");
+        // Currently there are queries that write to some subsystem that are allowed on a replica
+        // ex. analyze graph stats
+        // There are probably others. We not to check all of them and figure out if they are allowed and what are
+        // they even doing here...
+
         // Write transaction to WAL while holding the engine lock to make sure
         // that committed transactions are sorted by the commit timestamp in the
         // WAL files. We supply the new commit timestamp to the function so that
@@ -836,18 +835,16 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
         // modifications before they are written to disk.
         // Replica can log only the write transaction received from Main
         // so the Wal files are consistent
-        if (is_main || desired_commit_timestamp.has_value()) {
-          could_replicate_all_sync_replicas =
-              mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_);  // protected by engine_guard
-        }
+        if (is_main_or_replica_write) {
+          could_replicate_all_sync_replicas = mem_storage->AppendToWal(transaction_, *commit_timestamp_,
+                                                                       std::move(db_acc));  // protected by engine_guard
 
-        // TODO: release lock, and update all deltas to have a local copy of the commit timestamp
-        MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!");
-        transaction_.commit_timestamp->store(*commit_timestamp_,
-                                             std::memory_order_release);  // protected by engine_guard
-        // Replica can only update the last commit timestamp with
-        // the commits received from main.
-        if (is_main || desired_commit_timestamp.has_value()) {
+          // TODO: release lock, and update all deltas to have a local copy of the commit timestamp
+          MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!");
+          transaction_.commit_timestamp->store(*commit_timestamp_,
+                                               std::memory_order_release);  // protected by engine_guard
+          // Replica can only update the last commit timestamp with
+          // the commits received from main.
           // Update the last commit timestamp
           mem_storage->repl_storage_state_.last_commit_timestamp_.store(
               *commit_timestamp_);  // protected by engine_guard
@@ -1283,7 +1280,9 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(
       mem_label_property_index->Vertices(label, property, lower_bound, upper_bound, view, storage_, &transaction_));
 }
 
-Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) {
+Transaction InMemoryStorage::CreateTransaction(
+    IsolationLevel isolation_level, StorageMode storage_mode,
+    memgraph::replication_coordination_glue::ReplicationRole replication_role) {
   // We acquire the transaction engine lock here because we access (and
   // modify) the transaction engine variables (`transaction_id` and
   // `timestamp`) below.
@@ -1298,7 +1297,7 @@ Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, S
     // of any query on replica to the last commited transaction
     // which is timestamp_ as only commit of transaction with writes
     // can change the value of it.
-    if (is_main) {
+    if (replication_role == memgraph::replication_coordination_glue::ReplicationRole::MAIN) {
       start_timestamp = timestamp_++;
     } else {
       start_timestamp = timestamp_;
@@ -1579,9 +1578,12 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_
   if (run_index_cleanup) {
     // This operation is very expensive as it traverses through all of the items
     // in every index every time.
-    indices_.RemoveObsoleteEntries(oldest_active_start_timestamp);
-    auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get());
-    mem_unique_constraints->RemoveObsoleteEntries(oldest_active_start_timestamp);
+    auto token = stop_source.get_token();
+    if (!token.stop_requested()) {
+      indices_.RemoveObsoleteEntries(oldest_active_start_timestamp, token);
+      auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get());
+      mem_unique_constraints->RemoveObsoleteEntries(oldest_active_start_timestamp, std::move(token));
+    }
   }
 
   {
@@ -1672,7 +1674,7 @@ StorageInfo InMemoryStorage::GetBaseInfo(bool force_directory) {
         --it;
         if (it != end && *it != "databases") {
           // Default DB points to the root (for back-compatibility); update to the "database" dir
-          return dir / "databases" / dbms::kDefaultDB;
+          return dir / dbms::kMultiTenantDir / dbms::kDefaultDB;
         }
       }
     }
@@ -1682,10 +1684,11 @@ StorageInfo InMemoryStorage::GetBaseInfo(bool force_directory) {
   return info;
 }
 
-StorageInfo InMemoryStorage::GetInfo(bool force_directory) {
+StorageInfo InMemoryStorage::GetInfo(bool force_directory,
+                                     memgraph::replication_coordination_glue::ReplicationRole replication_role) {
   StorageInfo info = GetBaseInfo(force_directory);
   {
-    auto access = Access(std::nullopt);
+    auto access = Access(replication_role);  // TODO: override isolation level?
     const auto &lbl = access->ListAllIndices();
     info.label_indices = lbl.label.size();
     info.label_property_indices = lbl.label_property.size();
@@ -1707,8 +1710,8 @@ bool InMemoryStorage::InitializeWalFile(memgraph::replication::ReplicationEpoch
   if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL)
     return false;
   if (!wal_file_) {
-    wal_file_.emplace(recovery_.wal_directory_, uuid_, epoch.id(), config_.items, name_id_mapper_.get(), wal_seq_num_++,
-                      &file_retainer_);
+    wal_file_.emplace(recovery_.wal_directory_, uuid_, epoch.id(), config_.salient.items, name_id_mapper_.get(),
+                      wal_seq_num_++, &file_retainer_);
   }
   return true;
 }
@@ -1732,7 +1735,8 @@ void InMemoryStorage::FinalizeWalFile() {
   }
 }
 
-bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) {
+bool InMemoryStorage::AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp,
+                                  DatabaseAccessProtector db_acc) {
   if (!InitializeWalFile(repl_storage_state_.epoch_)) {
     return true;
   }
@@ -1740,7 +1744,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
   // A single transaction will always be contained in a single WAL file.
   auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire);
 
-  repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber(), this);
+  repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber(), this, db_acc);
 
   auto append_deltas = [&](auto callback) {
     // Helper lambda that traverses the delta chain on order to find the first
@@ -1889,26 +1893,15 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
     }
   };
 
-  append_deltas([&](const Delta &delta, const auto &parent, uint64_t timestamp) {
-    wal_file_->AppendDelta(delta, parent, timestamp);
-    repl_storage_state_.AppendDelta(delta, parent, timestamp);
-  });
-
-  // Add a delta that indicates that the transaction is fully written to the WAL
-  // file.replication_clients_.WithLock
-  wal_file_->AppendTransactionEnd(final_commit_timestamp);
-  FinalizeWalFile();
-
-  return repl_storage_state_.FinalizeTransaction(final_commit_timestamp, this);
-}
-
-bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp) {
-  if (!InitializeWalFile(repl_storage_state_.epoch_)) {
-    return true;
+  // Handle MVCC deltas
+  if (!transaction.deltas.use().empty()) {
+    append_deltas([&](const Delta &delta, const auto &parent, uint64_t timestamp) {
+      wal_file_->AppendDelta(delta, parent, timestamp);
+      repl_storage_state_.AppendDelta(delta, parent, timestamp);
+    });
   }
 
-  repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber(), this);
-
+  // Handle metadata deltas
   for (const auto &md_delta : transaction.md_deltas) {
     switch (md_delta.action) {
       case MetadataDelta::Action::LABEL_INDEX_CREATE: {
@@ -1978,7 +1971,7 @@ bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction,
   wal_file_->AppendTransactionEnd(final_commit_timestamp);
   FinalizeWalFile();
 
-  return repl_storage_state_.FinalizeTransaction(final_commit_timestamp, this);
+  return repl_storage_state_.FinalizeTransaction(final_commit_timestamp, this, std::move(db_acc));
 }
 
 void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
@@ -2012,11 +2005,16 @@ void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOpera
   return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp);
 }
 
-utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot() {
+utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot(
+    memgraph::replication_coordination_glue::ReplicationRole replication_role) {
+  if (replication_role == memgraph::replication_coordination_glue::ReplicationRole::REPLICA) {
+    return InMemoryStorage::CreateSnapshotError::DisabledForReplica;
+  }
   auto const &epoch = repl_storage_state_.epoch_;
   auto snapshot_creator = [this, &epoch]() {
     utils::Timer timer;
-    auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_);
+    auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_,
+                                         memgraph::replication_coordination_glue::ReplicationRole::MAIN);
     durability::CreateSnapshot(this, &transaction, recovery_.snapshot_directory_, recovery_.wal_directory_, &vertices_,
                                &edges_, uuid_, epoch, repl_storage_state_.history, &file_retainer_);
     // Finalize snapshot transaction.
@@ -2104,17 +2102,19 @@ utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::UnlockPath()
   return true;
 }
 
-std::unique_ptr<Storage::Accessor> InMemoryStorage::Access(std::optional<IsolationLevel> override_isolation_level,
-                                                           bool is_main) {
+std::unique_ptr<Storage::Accessor> InMemoryStorage::Access(
+    memgraph::replication_coordination_glue::ReplicationRole replication_role,
+    std::optional<IsolationLevel> override_isolation_level) {
   return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::shared_access, this,
                                                                 override_isolation_level.value_or(isolation_level_),
-                                                                storage_mode_, is_main});
+                                                                storage_mode_, replication_role});
 }
-std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
-                                                                 bool is_main) {
+std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(
+    memgraph::replication_coordination_glue::ReplicationRole replication_role,
+    std::optional<IsolationLevel> override_isolation_level) {
   return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::unique_access, this,
                                                                 override_isolation_level.value_or(isolation_level_),
-                                                                storage_mode_, is_main});
+                                                                storage_mode_, replication_role});
 }
 
 void InMemoryStorage::CreateSnapshotHandler(
@@ -2134,8 +2134,11 @@ void InMemoryStorage::CreateSnapshotHandler(
 
   // Run the snapshot thread (if enabled)
   if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
-    snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval,
-                         [this]() { this->create_snapshot_handler(); });
+    snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this, token = stop_source.get_token()]() {
+      if (!token.stop_requested()) {
+        this->create_snapshot_handler();
+      }
+    });
   }
 }
 IndicesInfo InMemoryStorage::InMemoryAccessor::ListAllIndices() const {
diff --git a/src/storage/v2/inmemory/storage.hpp b/src/storage/v2/inmemory/storage.hpp
index 2d2837467..6f8806c26 100644
--- a/src/storage/v2/inmemory/storage.hpp
+++ b/src/storage/v2/inmemory/storage.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -60,7 +60,6 @@ class InMemoryStorage final : public Storage {
   /// @throw std::system_error
   /// @throw std::bad_alloc
   explicit InMemoryStorage(Config config = Config());
-  InMemoryStorage(Config config, StorageMode storage_mode);
 
   InMemoryStorage(const InMemoryStorage &) = delete;
   InMemoryStorage(InMemoryStorage &&) = delete;
@@ -74,7 +73,8 @@ class InMemoryStorage final : public Storage {
     friend class InMemoryStorage;
 
     explicit InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
-                              StorageMode storage_mode, bool is_main = true);
+                              StorageMode storage_mode,
+                              memgraph::replication_coordination_glue::ReplicationRole replication_role);
 
    public:
     InMemoryAccessor(const InMemoryAccessor &) = delete;
@@ -215,8 +215,8 @@ class InMemoryStorage final : public Storage {
     /// case the transaction is automatically aborted.
     /// @throw std::bad_alloc
     // NOLINTNEXTLINE(google-default-arguments)
-    utils::BasicResult<StorageManipulationError, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {},
-                                                              bool is_main = true) override;
+    utils::BasicResult<StorageManipulationError, void> Commit(CommitReplArgs reparg = {},
+                                                              DatabaseAccessProtector db_acc = {}) override;
 
     /// @throw std::bad_alloc
     void Abort() override;
@@ -302,7 +302,7 @@ class InMemoryStorage final : public Storage {
     /// @throw std::bad_alloc
     Result<EdgeAccessor> CreateEdgeEx(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid);
 
-    Config::Items config_;
+    SalientConfig::Items config_;
   };
 
   class ReplicationAccessor final : public InMemoryAccessor {
@@ -323,12 +323,11 @@ class InMemoryStorage final : public Storage {
   };
 
   using Storage::Access;
-  std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level,
-                                            bool is_main) override;
-
+  std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role,
+                                   std::optional<IsolationLevel> override_isolation_level) override;
   using Storage::UniqueAccess;
-  std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
-                                                  bool is_main) override;
+  std::unique_ptr<Accessor> UniqueAccess(memgraph::replication_coordination_glue::ReplicationRole replication_role,
+                                         std::optional<IsolationLevel> override_isolation_level) override;
 
   void FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) override;
 
@@ -336,12 +335,13 @@ class InMemoryStorage final : public Storage {
   utils::FileRetainer::FileLockerAccessor::ret_type LockPath();
   utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath();
 
-  utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot();
+  utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot(
+      memgraph::replication_coordination_glue::ReplicationRole replication_role);
 
   void CreateSnapshotHandler(std::function<utils::BasicResult<InMemoryStorage::CreateSnapshotError>()> cb);
 
-  using Storage::CreateTransaction;
-  Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) override;
+  Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode,
+                                memgraph::replication_coordination_glue::ReplicationRole replication_role) override;
 
   void SetStorageMode(StorageMode storage_mode);
 
@@ -366,12 +366,12 @@ class InMemoryStorage final : public Storage {
   void FinalizeWalFile();
 
   StorageInfo GetBaseInfo(bool force_directory) override;
-  StorageInfo GetInfo(bool force_directory) override;
+  StorageInfo GetInfo(bool force_directory,
+                      memgraph::replication_coordination_glue::ReplicationRole replication_role) override;
 
   /// Return true in all cases excepted if any sync replicas have not sent confirmation.
-  [[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp);
-  /// Return true in all cases excepted if any sync replicas have not sent confirmation.
-  [[nodiscard]] bool AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp);
+  [[nodiscard]] bool AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp,
+                                 DatabaseAccessProtector db_acc);
   /// Return true in all cases excepted if any sync replicas have not sent confirmation.
   void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
                                  uint64_t final_commit_timestamp);
@@ -427,7 +427,6 @@ class InMemoryStorage final : public Storage {
   std::optional<CommitLog> commit_log_;
 
   utils::Scheduler gc_runner_;
-  utils::Scheduler gc_jemalloc_runner_;
   std::mutex gc_lock_;
 
   using BondPmrLd = Bond<utils::pmr::list<Delta>>;
@@ -463,6 +462,9 @@ class InMemoryStorage final : public Storage {
 
   // Moved the create snapshot to a user defined handler so we can remove the global replication state from the storage
   std::function<void()> create_snapshot_handler{};
+
+  // A way to tell async operation to stop
+  std::stop_source stop_source;
 };
 
 }  // namespace memgraph::storage
diff --git a/src/storage/v2/inmemory/unique_constraints.cpp b/src/storage/v2/inmemory/unique_constraints.cpp
index 76cda1730..667d0229f 100644
--- a/src/storage/v2/inmemory/unique_constraints.cpp
+++ b/src/storage/v2/inmemory/unique_constraints.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -15,6 +15,7 @@
 #include "storage/v2/constraints/utils.hpp"
 #include "storage/v2/durability/recovery_type.hpp"
 #include "storage/v2/id_types.hpp"
+#include "utils/counter.hpp"
 #include "utils/logging.hpp"
 #include "utils/skip_list.hpp"
 namespace memgraph::storage {
@@ -487,10 +488,18 @@ std::vector<std::pair<LabelId, std::set<PropertyId>>> InMemoryUniqueConstraints:
   return ret;
 }
 
-void InMemoryUniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
+void InMemoryUniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) {
+  auto maybe_stop = utils::ResettableCounter<2048>();
+
   for (auto &[label_props, storage] : constraints_) {
+    // before starting constraint, check if stop_requested
+    if (token.stop_requested()) return;
+
     auto acc = storage.access();
     for (auto it = acc.begin(); it != acc.end();) {
+      // Hot loop, don't check stop_requested every time
+      if (maybe_stop() && token.stop_requested()) return;
+
       auto next_it = it;
       ++next_it;
 
diff --git a/src/storage/v2/inmemory/unique_constraints.hpp b/src/storage/v2/inmemory/unique_constraints.hpp
index 15107f131..27fae1b30 100644
--- a/src/storage/v2/inmemory/unique_constraints.hpp
+++ b/src/storage/v2/inmemory/unique_constraints.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -122,7 +122,7 @@ class InMemoryUniqueConstraints : public UniqueConstraints {
   std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const override;
 
   /// GC method that removes outdated entries from constraints' storages.
-  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
+  void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token);
 
   void Clear() override;
 
diff --git a/src/storage/v2/property_store.cpp b/src/storage/v2/property_store.cpp
index 530d5f5f6..427998fbe 100644
--- a/src/storage/v2/property_store.cpp
+++ b/src/storage/v2/property_store.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -1050,14 +1050,11 @@ bool PropertyStore::HasProperty(PropertyId property) const {
   return ExistsSpecificProperty(&reader, property) == ExpectedPropertyStatus::EQUAL;
 }
 
-/// TODO: andi write a unit test for it
 bool PropertyStore::HasAllProperties(const std::set<PropertyId> &properties) const {
   return std::all_of(properties.begin(), properties.end(), [this](const auto &prop) { return HasProperty(prop); });
 }
 
-/// TODO: andi write a unit test for it
 bool PropertyStore::HasAllPropertyValues(const std::vector<PropertyValue> &property_values) const {
-  /// TODO: andi extract this into a private method
   auto property_map = Properties();
   std::vector<PropertyValue> all_property_values;
   transform(property_map.begin(), property_map.end(), back_inserter(all_property_values),
diff --git a/src/storage/v2/property_store.hpp b/src/storage/v2/property_store.hpp
index 6a458641b..c217cbd81 100644
--- a/src/storage/v2/property_store.hpp
+++ b/src/storage/v2/property_store.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -55,7 +55,6 @@ class PropertyStore {
 
   /// Checks whether all property values in the vector `property_values` exist in the store. The time
   /// complexity of this function is O(n^2).
-  /// TODO: andi Not so sure it is quadratic complexity
   bool HasAllPropertyValues(const std::vector<PropertyValue> &property_values) const;
 
   /// Extracts property values for all property ids in the set `properties`. The time
diff --git a/src/storage/v2/property_value.hpp b/src/storage/v2/property_value.hpp
index 05ab1d3db..727c75377 100644
--- a/src/storage/v2/property_value.hpp
+++ b/src/storage/v2/property_value.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -123,21 +123,21 @@ class PropertyValue {
   // value getters for primitive types
   /// @throw PropertyValueException if value isn't of correct type.
   bool ValueBool() const {
-    if (type_ != Type::Bool) {
+    if (type_ != Type::Bool) [[unlikely]] {
       throw PropertyValueException("The value isn't a bool!");
     }
     return bool_v;
   }
   /// @throw PropertyValueException if value isn't of correct type.
   int64_t ValueInt() const {
-    if (type_ != Type::Int) {
+    if (type_ != Type::Int) [[unlikely]] {
       throw PropertyValueException("The value isn't an int!");
     }
     return int_v;
   }
   /// @throw PropertyValueException if value isn't of correct type.
   double ValueDouble() const {
-    if (type_ != Type::Double) {
+    if (type_ != Type::Double) [[unlikely]] {
       throw PropertyValueException("The value isn't a double!");
     }
     return double_v;
@@ -145,7 +145,7 @@ class PropertyValue {
 
   /// @throw PropertyValueException if value isn't of correct type.
   TemporalData ValueTemporalData() const {
-    if (type_ != Type::TemporalData) {
+    if (type_ != Type::TemporalData) [[unlikely]] {
       throw PropertyValueException("The value isn't a temporal data!");
     }
 
@@ -155,7 +155,7 @@ class PropertyValue {
   // const value getters for non-primitive types
   /// @throw PropertyValueException if value isn't of correct type.
   const std::string &ValueString() const {
-    if (type_ != Type::String) {
+    if (type_ != Type::String) [[unlikely]] {
       throw PropertyValueException("The value isn't a string!");
     }
     return string_v;
@@ -163,7 +163,7 @@ class PropertyValue {
 
   /// @throw PropertyValueException if value isn't of correct type.
   const std::vector<PropertyValue> &ValueList() const {
-    if (type_ != Type::List) {
+    if (type_ != Type::List) [[unlikely]] {
       throw PropertyValueException("The value isn't a list!");
     }
     return list_v;
@@ -171,7 +171,7 @@ class PropertyValue {
 
   /// @throw PropertyValueException if value isn't of correct type.
   const std::map<std::string, PropertyValue> &ValueMap() const {
-    if (type_ != Type::Map) {
+    if (type_ != Type::Map) [[unlikely]] {
       throw PropertyValueException("The value isn't a map!");
     }
     return map_v;
@@ -180,7 +180,7 @@ class PropertyValue {
   // reference value getters for non-primitive types
   /// @throw PropertyValueException if value isn't of correct type.
   std::string &ValueString() {
-    if (type_ != Type::String) {
+    if (type_ != Type::String) [[unlikely]] {
       throw PropertyValueException("The value isn't a string!");
     }
     return string_v;
@@ -188,7 +188,7 @@ class PropertyValue {
 
   /// @throw PropertyValueException if value isn't of correct type.
   std::vector<PropertyValue> &ValueList() {
-    if (type_ != Type::List) {
+    if (type_ != Type::List) [[unlikely]] {
       throw PropertyValueException("The value isn't a list!");
     }
     return list_v;
@@ -196,7 +196,7 @@ class PropertyValue {
 
   /// @throw PropertyValueException if value isn't of correct type.
   std::map<std::string, PropertyValue> &ValueMap() {
-    if (type_ != Type::Map) {
+    if (type_ != Type::Map) [[unlikely]] {
       throw PropertyValueException("The value isn't a map!");
     }
     return map_v;
@@ -279,7 +279,7 @@ inline bool operator==(const PropertyValue &first, const PropertyValue &second)
     case PropertyValue::Type::Bool:
       return first.ValueBool() == second.ValueBool();
     case PropertyValue::Type::Int:
-      if (second.type() == PropertyValue::Type::Double) {
+      if (second.type() == PropertyValue::Type::Double) [[unlikely]] {
         return first.ValueInt() == second.ValueDouble();
       } else {
         return first.ValueInt() == second.ValueInt();
@@ -310,7 +310,7 @@ inline bool operator<(const PropertyValue &first, const PropertyValue &second) n
     case PropertyValue::Type::Bool:
       return first.ValueBool() < second.ValueBool();
     case PropertyValue::Type::Int:
-      if (second.type() == PropertyValue::Type::Double) {
+      if (second.type() == PropertyValue::Type::Double) [[unlikely]] {
         return first.ValueInt() < second.ValueDouble();
       } else {
         return first.ValueInt() < second.ValueInt();
@@ -363,36 +363,35 @@ inline PropertyValue::PropertyValue(const PropertyValue &other) : type_(other.ty
   }
 }
 
-inline PropertyValue::PropertyValue(PropertyValue &&other) noexcept : type_(other.type_) {
-  switch (other.type_) {
+inline PropertyValue::PropertyValue(PropertyValue &&other) noexcept : type_(std::exchange(other.type_, Type::Null)) {
+  switch (type_) {
     case Type::Null:
       break;
     case Type::Bool:
-      this->bool_v = other.bool_v;
+      bool_v = other.bool_v;
       break;
     case Type::Int:
-      this->int_v = other.int_v;
+      int_v = other.int_v;
       break;
     case Type::Double:
-      this->double_v = other.double_v;
+      double_v = other.double_v;
       break;
     case Type::String:
-      new (&string_v) std::string(std::move(other.string_v));
+      std::construct_at(&string_v, std::move(other.string_v));
+      std::destroy_at(&other.string_v);
       break;
     case Type::List:
-      new (&list_v) std::vector<PropertyValue>(std::move(other.list_v));
+      std::construct_at(&list_v, std::move(other.list_v));
+      std::destroy_at(&other.list_v);
       break;
     case Type::Map:
-      new (&map_v) std::map<std::string, PropertyValue>(std::move(other.map_v));
+      std::construct_at(&map_v, std::move(other.map_v));
+      std::destroy_at(&other.map_v);
       break;
     case Type::TemporalData:
-      this->temporal_data_v = other.temporal_data_v;
+      temporal_data_v = other.temporal_data_v;
       break;
   }
-
-  // reset the type of other
-  other.DestroyValue();
-  other.type_ = Type::Null;
 }
 
 inline PropertyValue &PropertyValue::operator=(const PropertyValue &other) {
@@ -431,46 +430,48 @@ inline PropertyValue &PropertyValue::operator=(const PropertyValue &other) {
 }
 
 inline PropertyValue &PropertyValue::operator=(PropertyValue &&other) noexcept {
-  if (this == &other) return *this;
+  if (type_ == other.type_) {
+    // maybe the same object, check if no work is required
+    if (this == &other) return *this;
 
-  DestroyValue();
-  type_ = other.type_;
-
-  switch (other.type_) {
-    case Type::Null:
-      break;
-    case Type::Bool:
-      this->bool_v = other.bool_v;
-      break;
-    case Type::Int:
-      this->int_v = other.int_v;
-      break;
-    case Type::Double:
-      this->double_v = other.double_v;
-      break;
-    case Type::String:
-      new (&string_v) std::string(std::move(other.string_v));
-      break;
-    case Type::List:
-      new (&list_v) std::vector<PropertyValue>(std::move(other.list_v));
-      break;
-    case Type::Map:
-      new (&map_v) std::map<std::string, PropertyValue>(std::move(other.map_v));
-      break;
-    case Type::TemporalData:
-      this->temporal_data_v = other.temporal_data_v;
-      break;
+    switch (type_) {
+      case Type::Null:
+        break;
+      case Type::Bool:
+        bool_v = other.bool_v;
+        break;
+      case Type::Int:
+        int_v = other.int_v;
+        break;
+      case Type::Double:
+        double_v = other.double_v;
+        break;
+      case Type::String:
+        string_v = std::move(other.string_v);
+        std::destroy_at(&other.string_v);
+        break;
+      case Type::List:
+        list_v = std::move(other.list_v);
+        std::destroy_at(&other.list_v);
+        break;
+      case Type::Map:
+        map_v = std::move(other.map_v);
+        std::destroy_at(&other.map_v);
+        break;
+      case Type::TemporalData:
+        temporal_data_v = other.temporal_data_v;
+        break;
+    }
+    other.type_ = Type::Null;
+    return *this;
+  } else {
+    std::destroy_at(this);
+    return *std::construct_at(std::launder(this), std::move(other));
   }
-
-  // reset the type of other
-  other.DestroyValue();
-  other.type_ = Type::Null;
-
-  return *this;
 }
 
 inline void PropertyValue::DestroyValue() noexcept {
-  switch (type_) {
+  switch (std::exchange(type_, Type::Null)) {
     // destructor for primitive types does nothing
     case Type::Null:
     case Type::Bool:
diff --git a/src/storage/v2/replication/global.hpp b/src/storage/v2/replication/global.hpp
index 7892fb990..ebcec1206 100644
--- a/src/storage/v2/replication/global.hpp
+++ b/src/storage/v2/replication/global.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -31,7 +31,7 @@ struct TimestampInfo {
 
 struct ReplicaInfo {
   std::string name;
-  memgraph::replication::ReplicationMode mode;
+  replication_coordination_glue::ReplicationMode mode;
   io::network::Endpoint endpoint;
   replication::ReplicaState state;
   TimestampInfo timestamp_info;
diff --git a/src/storage/v2/replication/replication_client.cpp b/src/storage/v2/replication/replication_client.cpp
index 3bc1b3d32..b68618e04 100644
--- a/src/storage/v2/replication/replication_client.cpp
+++ b/src/storage/v2/replication/replication_client.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,14 +10,13 @@
 // licenses/APL.txt.
 
 #include "replication/replication_client.hpp"
-#include "storage/v2/durability/durability.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/storage.hpp"
 #include "utils/exceptions.hpp"
+#include "utils/on_scope_exit.hpp"
 #include "utils/variant_helpers.hpp"
 
 #include <algorithm>
-#include <type_traits>
 
 namespace {
 template <typename>
@@ -29,14 +28,26 @@ namespace memgraph::storage {
 ReplicationStorageClient::ReplicationStorageClient(::memgraph::replication::ReplicationClient &client)
     : client_{client} {}
 
-void ReplicationStorageClient::CheckReplicaState(Storage *storage) {
+void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAccessProtector db_acc) {
   uint64_t current_commit_timestamp{kTimestampInitialId};
 
   auto &replStorageState = storage->repl_storage_state_;
-  auto stream{client_.rpc_client_.Stream<replication::HeartbeatRpc>(
-      storage->id(), replStorageState.last_commit_timestamp_, std::string{replStorageState.epoch_.id()})};
 
-  const auto replica = stream.AwaitResponse();
+  auto hb_stream{client_.rpc_client_.Stream<replication::HeartbeatRpc>(
+      storage->uuid(), replStorageState.last_commit_timestamp_, std::string{replStorageState.epoch_.id()})};
+  const auto replica = hb_stream.AwaitResponse();
+
+#ifdef MG_ENTERPRISE       // Multi-tenancy is only supported in enterprise
+  if (!replica.success) {  // Replica is missing the current database
+    client_.state_.WithLock([&](auto &state) {
+      spdlog::debug("Replica '{}' missing database '{}' - '{}'", client_.name_, storage->name(),
+                    std::string{storage->uuid()});
+      state = memgraph::replication::ReplicationClient::State::BEHIND;
+    });
+    return;
+  }
+#endif
+
   std::optional<uint64_t> branching_point;
   if (replica.epoch_id != replStorageState.epoch_.id() && replica.current_commit_timestamp != kTimestampInitialId) {
     auto const &history = replStorageState.history;
@@ -56,6 +67,7 @@ void ReplicationStorageClient::CheckReplicaState(Storage *storage) {
         "now hold unique data. Please resolve data conflicts and start the "
         "replication on a clean instance.",
         client_.name_, client_.name_, client_.name_);
+    // TODO: (andi) Talk about renaming MAYBE_BEHIND to branching
     // State not updated, hence in MAYBE_BEHIND state
     return;
   }
@@ -70,8 +82,9 @@ void ReplicationStorageClient::CheckReplicaState(Storage *storage) {
     } else {
       spdlog::debug("Replica '{}' is behind", client_.name_);
       state = replication::ReplicaState::RECOVERY;
-      client_.thread_pool_.AddTask(
-          [storage, current_commit_timestamp, this] { this->RecoverReplica(current_commit_timestamp, storage); });
+      client_.thread_pool_.AddTask([storage, current_commit_timestamp, gk = std::move(db_acc), this] {
+        this->RecoverReplica(current_commit_timestamp, storage);
+      });
     }
   });
 }
@@ -82,16 +95,18 @@ TimestampInfo ReplicationStorageClient::GetTimestampInfo(Storage const *storage)
   info.current_number_of_timestamp_behind_master = 0;
 
   try {
-    auto stream{client_.rpc_client_.Stream<replication::TimestampRpc>(storage->id())};
+    auto stream{client_.rpc_client_.Stream<replication::TimestampRpc>(storage->uuid())};
     const auto response = stream.AwaitResponse();
     const auto is_success = response.success;
-    if (!is_success) {
-      replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; });
-      LogRpcFailure();
-    }
+
     auto main_time_stamp = storage->repl_storage_state_.last_commit_timestamp_.load();
     info.current_timestamp_of_replica = response.current_commit_timestamp;
     info.current_number_of_timestamp_behind_master = response.current_commit_timestamp - main_time_stamp;
+
+    if (!is_success || info.current_number_of_timestamp_behind_master != 0) {
+      replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; });
+      LogRpcFailure();
+    }
   } catch (const rpc::RpcFailedException &) {
     replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; });
     LogRpcFailure();  // mutex already unlocked, if the new enqueued task dispatches immediately it probably
@@ -106,13 +121,15 @@ void ReplicationStorageClient::LogRpcFailure() {
       utils::MessageWithLink("Couldn't replicate data to {}.", client_.name_, "https://memgr.ph/replication"));
 }
 
-void ReplicationStorageClient::TryCheckReplicaStateAsync(Storage *storage) {
-  client_.thread_pool_.AddTask([storage, this] { this->TryCheckReplicaStateSync(storage); });
+void ReplicationStorageClient::TryCheckReplicaStateAsync(Storage *storage, DatabaseAccessProtector db_acc) {
+  client_.thread_pool_.AddTask([storage, db_acc = std::move(db_acc), this]() mutable {
+    this->TryCheckReplicaStateSync(storage, std::move(db_acc));
+  });
 }
 
-void ReplicationStorageClient::TryCheckReplicaStateSync(Storage *storage) {
+void ReplicationStorageClient::TryCheckReplicaStateSync(Storage *storage, DatabaseAccessProtector db_acc) {
   try {
-    CheckReplicaState(storage);
+    UpdateReplicaState(storage, std::move(db_acc));
   } catch (const rpc::VersionMismatchRpcFailedException &) {
     replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; });
     spdlog::error(
@@ -126,7 +143,8 @@ void ReplicationStorageClient::TryCheckReplicaStateSync(Storage *storage) {
   }
 }
 
-void ReplicationStorageClient::StartTransactionReplication(const uint64_t current_wal_seq_num, Storage *storage) {
+void ReplicationStorageClient::StartTransactionReplication(const uint64_t current_wal_seq_num, Storage *storage,
+                                                           DatabaseAccessProtector db_acc) {
   auto locked_state = replica_state_.Lock();
   switch (*locked_state) {
     using enum replication::ReplicaState;
@@ -150,7 +168,7 @@ void ReplicationStorageClient::StartTransactionReplication(const uint64_t curren
     case MAYBE_BEHIND:
       spdlog::error(
           utils::MessageWithLink("Couldn't replicate data to {}.", client_.name_, "https://memgr.ph/replication"));
-      TryCheckReplicaStateAsync(storage);
+      TryCheckReplicaStateAsync(storage, std::move(db_acc));
       return;
     case READY:
       MG_ASSERT(!replica_stream_);
@@ -165,7 +183,7 @@ void ReplicationStorageClient::StartTransactionReplication(const uint64_t curren
   }
 }
 
-bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage) {
+bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage, DatabaseAccessProtector db_acc) {
   // We can only check the state because it guarantees to be only
   // valid during a single transaction replication (if the assumption
   // that this and other transaction replication functions can only be
@@ -174,18 +192,26 @@ bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage)
     return false;
   }
 
-  if (replica_stream_->IsDefunct()) return false;
+  if (!replica_stream_ || replica_stream_->IsDefunct()) {
+    replica_state_.WithLock([this](auto &state) {
+      replica_stream_.reset();
+      state = replication::ReplicaState::MAYBE_BEHIND;
+    });
+    LogRpcFailure();
+    return false;
+  }
 
-  auto task = [storage, this]() {
+  auto task = [storage, db_acc = std::move(db_acc), this]() mutable {
     MG_ASSERT(replica_stream_, "Missing stream for transaction deltas");
     try {
       auto response = replica_stream_->Finalize();
-      return replica_state_.WithLock([storage, &response, this](auto &state) {
+      return replica_state_.WithLock([storage, &response, db_acc = std::move(db_acc), this](auto &state) mutable {
         replica_stream_.reset();
         if (!response.success || state == replication::ReplicaState::RECOVERY) {
           state = replication::ReplicaState::RECOVERY;
-          client_.thread_pool_.AddTask(
-              [storage, &response, this] { this->RecoverReplica(response.current_commit_timestamp, storage); });
+          client_.thread_pool_.AddTask([storage, &response, db_acc = std::move(db_acc), this] {
+            this->RecoverReplica(response.current_commit_timestamp, storage);
+          });
           return false;
         }
         state = replication::ReplicaState::READY;
@@ -201,17 +227,17 @@ bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage)
     }
   };
 
-  if (client_.mode_ == memgraph::replication::ReplicationMode::ASYNC) {
-    client_.thread_pool_.AddTask([task = std::move(task)] { (void)task(); });
+  if (client_.mode_ == replication_coordination_glue::ReplicationMode::ASYNC) {
+    client_.thread_pool_.AddTask([task = std::move(task)]() mutable { (void)task(); });
     return true;
   }
 
   return task();
 }
 
-void ReplicationStorageClient::Start(Storage *storage) {
-  spdlog::trace("Replication client started for database \"{}\"", storage->id());
-  TryCheckReplicaStateSync(storage);
+void ReplicationStorageClient::Start(Storage *storage, DatabaseAccessProtector db_acc) {
+  spdlog::trace("Replication client started for database \"{}\"", storage->name());
+  TryCheckReplicaStateSync(storage, std::move(db_acc));
 }
 
 void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph::storage::Storage *storage) {
@@ -233,12 +259,12 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph:
         std::visit(utils::Overloaded{
                        [&replica_commit, mem_storage, &rpcClient](RecoverySnapshot const &snapshot) {
                          spdlog::debug("Sending the latest snapshot file: {}", snapshot);
-                         auto response = TransferSnapshot(mem_storage->id(), rpcClient, snapshot);
+                         auto response = TransferSnapshot(mem_storage->uuid(), rpcClient, snapshot);
                          replica_commit = response.current_commit_timestamp;
                        },
                        [&replica_commit, mem_storage, &rpcClient](RecoveryWals const &wals) {
                          spdlog::debug("Sending the latest wal files");
-                         auto response = TransferWalFiles(mem_storage->id(), rpcClient, wals);
+                         auto response = TransferWalFiles(mem_storage->uuid(), rpcClient, wals);
                          replica_commit = response.current_commit_timestamp;
                          spdlog::debug("Wal files successfully transferred.");
                        },
@@ -246,11 +272,11 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph:
                          std::unique_lock transaction_guard(mem_storage->engine_lock_);
                          if (mem_storage->wal_file_ &&
                              mem_storage->wal_file_->SequenceNumber() == current_wal.current_wal_seq_num) {
+                           utils::OnScopeExit on_exit([mem_storage]() { mem_storage->wal_file_->EnableFlushing(); });
                            mem_storage->wal_file_->DisableFlushing();
                            transaction_guard.unlock();
                            spdlog::debug("Sending current wal file");
                            replica_commit = ReplicateCurrentWal(mem_storage, rpcClient, *mem_storage->wal_file_);
-                           mem_storage->wal_file_->EnableFlushing();
                          } else {
                            spdlog::debug("Cannot recover using current wal file");
                          }
@@ -291,14 +317,14 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph:
 ReplicaStream::ReplicaStream(Storage *storage, rpc::Client &rpc_client, const uint64_t current_seq_num)
     : storage_{storage},
       stream_(rpc_client.Stream<replication::AppendDeltasRpc>(
-          storage->id(), storage->repl_storage_state_.last_commit_timestamp_.load(), current_seq_num)) {
+          storage->uuid(), storage->repl_storage_state_.last_commit_timestamp_.load(), current_seq_num)) {
   replication::Encoder encoder{stream_.GetBuilder()};
   encoder.WriteString(storage->repl_storage_state_.epoch_.id());
 }
 
 void ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t final_commit_timestamp) {
   replication::Encoder encoder(stream_.GetBuilder());
-  EncodeDelta(&encoder, storage_->name_id_mapper_.get(), storage_->config_.items, delta, vertex,
+  EncodeDelta(&encoder, storage_->name_id_mapper_.get(), storage_->config_.salient.items, delta, vertex,
               final_commit_timestamp);
 }
 
diff --git a/src/storage/v2/replication/replication_client.hpp b/src/storage/v2/replication/replication_client.hpp
index 3d2c019e9..fbcffe422 100644
--- a/src/storage/v2/replication/replication_client.hpp
+++ b/src/storage/v2/replication/replication_client.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,9 +13,10 @@
 
 #include "replication/config.hpp"
 #include "replication/epoch.hpp"
-#include "replication/messages.hpp"
 #include "replication/replication_client.hpp"
+#include "replication_coordination_glue/messages.hpp"
 #include "rpc/client.hpp"
+#include "storage/v2/database_access.hpp"
 #include "storage/v2/durability/storage_global_operation.hpp"
 #include "storage/v2/id_types.hpp"
 #include "storage/v2/indices/label_index_stats.hpp"
@@ -93,20 +94,34 @@ class ReplicationStorageClient {
   ~ReplicationStorageClient() = default;
 
   // TODO Remove the client related functions
-  auto Mode() const -> memgraph::replication::ReplicationMode { return client_.mode_; }
+  auto Mode() const -> memgraph::replication_coordination_glue::ReplicationMode { return client_.mode_; }
   auto Name() const -> std::string const & { return client_.name_; }
   auto Endpoint() const -> io::network::Endpoint const & { return client_.rpc_client_.Endpoint(); }
 
   auto State() const -> replication::ReplicaState { return replica_state_.WithLock(std::identity()); }
   auto GetTimestampInfo(Storage const *storage) -> TimestampInfo;
 
-  void Start(Storage *storage);
-  void StartTransactionReplication(uint64_t current_wal_seq_num, Storage *storage);
+  /**
+   * @brief Check the replica state
+   *
+   * @param storage pointer to the storage associated with the client
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   */
+  void Start(Storage *storage, DatabaseAccessProtector db_acc);
+
+  /**
+   * @brief Start a new transaction replication (open up a stream)
+   *
+   * @param current_wal_seq_num
+   * @param storage pointer to the storage associated with the client
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   */
+  void StartTransactionReplication(uint64_t current_wal_seq_num, Storage *storage, DatabaseAccessProtector db_acc);
 
   // Replication clients can be removed at any point
   // so to avoid any complexity of checking if the client was removed whenever
   // we want to send part of transaction and to avoid adding some GC logic this
-  // function will run a callback if, after previously callling
+  // function will run a callback if, after previously calling
   // StartTransactionReplication, stream is created.
   template <InvocableWithStream F>
   void IfStreamingTransaction(F &&callback) {
@@ -117,26 +132,69 @@ class ReplicationStorageClient {
     if (State() != replication::ReplicaState::REPLICATING) {
       return;
     }
-    if (replica_stream_->IsDefunct()) return;
+    if (!replica_stream_ || replica_stream_->IsDefunct()) {
+      replica_state_.WithLock([this](auto &state) {
+        replica_stream_.reset();
+        state = replication::ReplicaState::MAYBE_BEHIND;
+      });
+      LogRpcFailure();
+      return;
+    }
     try {
       callback(*replica_stream_);  // failure state what if not streaming (std::nullopt)
     } catch (const rpc::RpcFailedException &) {
-      return replica_state_.WithLock([](auto &state) { state = replication::ReplicaState::MAYBE_BEHIND; });
+      replica_state_.WithLock([](auto &state) { state = replication::ReplicaState::MAYBE_BEHIND; });
       LogRpcFailure();
+      return;
     }
   }
 
-  // Return whether the transaction could be finalized on the replication client or not.
-  [[nodiscard]] bool FinalizeTransactionReplication(Storage *storage);
+  /**
+   * @brief Return whether the transaction could be finalized on the replication client or not.
+   *
+   * @param storage pointer to the storage associated with the client
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   * @return true
+   * @return false
+   */
+  [[nodiscard]] bool FinalizeTransactionReplication(Storage *storage, DatabaseAccessProtector db_acc);
+
+  /**
+   * @brief Asynchronously try to check the replica state and start a recovery thread if necessary
+   *
+   * @param storage pointer to the storage associated with the client
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   */
+  void TryCheckReplicaStateAsync(Storage *storage, DatabaseAccessProtector db_acc);  // TODO Move back to private
+
+  auto &Client() { return client_; }
 
-  void TryCheckReplicaStateAsync(Storage *storage);  // TODO Move back to private
  private:
+  /**
+   * @brief Get necessary recovery steps and execute them.
+   *
+   * @param replica_commit the commit up to which we should recover to
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   */
   void RecoverReplica(uint64_t replica_commit, memgraph::storage::Storage *storage);
 
-  void CheckReplicaState(Storage *storage);
+  /**
+   * @brief Check replica state
+   *
+   * @param storage pointer to the storage associated with the client
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   */
+  void UpdateReplicaState(Storage *storage, DatabaseAccessProtector db_acc);
+
   void LogRpcFailure();
-  void TryCheckReplicaStateSync(Storage *storage);
-  void FrequentCheck(Storage *storage);
+
+  /**
+   * @brief Synchronously try to check the replica state and start a recovery thread if necessary
+   *
+   * @param storage pointer to the storage associated with the client
+   * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage
+   */
+  void TryCheckReplicaStateSync(Storage *storage, DatabaseAccessProtector db_acc);
 
   ::memgraph::replication::ReplicationClient &client_;
   // TODO Do not store the stream, make is a local variable
diff --git a/src/storage/v2/replication/replication_storage_state.cpp b/src/storage/v2/replication/replication_storage_state.cpp
index a443c7171..25cf484c9 100644
--- a/src/storage/v2/replication/replication_storage_state.cpp
+++ b/src/storage/v2/replication/replication_storage_state.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,10 +16,11 @@
 
 namespace memgraph::storage {
 
-void ReplicationStorageState::InitializeTransaction(uint64_t seq_num, Storage *storage) {
-  replication_clients_.WithLock([=](auto &clients) {
+void ReplicationStorageState::InitializeTransaction(uint64_t seq_num, Storage *storage,
+                                                    DatabaseAccessProtector db_acc) {
+  replication_clients_.WithLock([=, db_acc = std::move(db_acc)](auto &clients) mutable {
     for (auto &client : clients) {
-      client->StartTransactionReplication(seq_num, storage);
+      client->StartTransactionReplication(seq_num, storage, std::move(db_acc));
     }
   });
 }
@@ -52,14 +53,18 @@ void ReplicationStorageState::AppendOperation(durability::StorageMetadataOperati
   });
 }
 
-bool ReplicationStorageState::FinalizeTransaction(uint64_t timestamp, Storage *storage) {
-  return replication_clients_.WithLock([=](auto &clients) {
+bool ReplicationStorageState::FinalizeTransaction(uint64_t timestamp, Storage *storage,
+                                                  DatabaseAccessProtector db_acc) {
+  return replication_clients_.WithLock([=, db_acc = std::move(db_acc)](auto &clients) mutable {
     bool finalized_on_all_replicas = true;
+    MG_ASSERT(clients.empty() || db_acc.has_value(),
+              "Any clients assumes we are MAIN, we should have gatekeeper_access_wrapper so we can correctly "
+              "handle ASYNC tasks");
     for (ReplicationClientPtr &client : clients) {
       client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(timestamp); });
-      const auto finalized = client->FinalizeTransactionReplication(storage);
+      const auto finalized = client->FinalizeTransactionReplication(storage, std::move(db_acc));
 
-      if (client->Mode() == memgraph::replication::ReplicationMode::SYNC) {
+      if (client->Mode() == replication_coordination_glue::ReplicationMode::SYNC) {
         finalized_on_all_replicas = finalized && finalized_on_all_replicas;
       }
     }
@@ -83,7 +88,8 @@ std::vector<ReplicaInfo> ReplicationStorageState::ReplicasInfo(const Storage *st
     std::vector<ReplicaInfo> replica_infos;
     replica_infos.reserve(clients.size());
     auto const asReplicaInfo = [storage](ReplicationClientPtr const &client) -> ReplicaInfo {
-      return {client->Name(), client->Mode(), client->Endpoint(), client->State(), client->GetTimestampInfo(storage)};
+      const auto ts = client->GetTimestampInfo(storage);
+      return {client->Name(), client->Mode(), client->Endpoint(), client->State(), ts};
     };
     std::transform(clients.begin(), clients.end(), std::back_inserter(replica_infos), asReplicaInfo);
     return replica_infos;
diff --git a/src/storage/v2/replication/replication_storage_state.hpp b/src/storage/v2/replication/replication_storage_state.hpp
index e3d6b94a0..adbf87aa9 100644
--- a/src/storage/v2/replication/replication_storage_state.hpp
+++ b/src/storage/v2/replication/replication_storage_state.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -25,6 +25,7 @@
 #include "replication/config.hpp"
 #include "replication/epoch.hpp"
 #include "replication/state.hpp"
+#include "storage/v2/database_access.hpp"
 #include "storage/v2/replication/enums.hpp"
 #include "storage/v2/replication/global.hpp"
 #include "storage/v2/replication/rpc.hpp"
@@ -39,13 +40,13 @@ class ReplicationStorageClient;
 
 struct ReplicationStorageState {
   // Only MAIN can send
-  void InitializeTransaction(uint64_t seq_num, Storage *storage);
+  void InitializeTransaction(uint64_t seq_num, Storage *storage, DatabaseAccessProtector db_acc);
   void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp);
   void AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp);
   void AppendOperation(durability::StorageMetadataOperation operation, LabelId label,
                        const std::set<PropertyId> &properties, const LabelIndexStats &stats,
                        const LabelPropertyIndexStats &property_stats, uint64_t final_commit_timestamp);
-  bool FinalizeTransaction(uint64_t timestamp, Storage *storage);
+  bool FinalizeTransaction(uint64_t timestamp, Storage *storage, DatabaseAccessProtector db_acc);
 
   // Getters
   auto GetReplicaState(std::string_view name) const -> std::optional<replication::ReplicaState>;
diff --git a/src/storage/v2/replication/rpc.cpp b/src/storage/v2/replication/rpc.cpp
index b722dfebf..27fc1a0d6 100644
--- a/src/storage/v2/replication/rpc.cpp
+++ b/src/storage/v2/replication/rpc.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,6 +10,9 @@
 // licenses/APL.txt.
 
 #include "storage/v2/replication/rpc.hpp"
+#include <cstdint>
+#include "slk/streams.hpp"
+#include "utils/enum.hpp"
 #include "utils/typeinfo.hpp"
 
 namespace memgraph {
@@ -56,6 +59,38 @@ void TimestampRes::Save(const TimestampRes &self, memgraph::slk::Builder *builde
   memgraph::slk::Save(self, builder);
 }
 void TimestampRes::Load(TimestampRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
+void CreateDatabaseReq::Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void CreateDatabaseReq::Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+void CreateDatabaseRes::Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void CreateDatabaseRes::Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+void DropDatabaseReq::Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void DropDatabaseReq::Load(DropDatabaseReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
+void DropDatabaseRes::Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void DropDatabaseRes::Load(DropDatabaseRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
+void SystemRecoveryReq::Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void SystemRecoveryReq::Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
+void SystemRecoveryRes::Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self, builder);
+}
+void SystemRecoveryRes::Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(self, reader);
+}
 
 }  // namespace storage::replication
 
@@ -95,18 +130,34 @@ constexpr utils::TypeInfo storage::replication::TimestampReq::kType{utils::TypeI
 constexpr utils::TypeInfo storage::replication::TimestampRes::kType{utils::TypeId::REP_TIMESTAMP_RES, "TimestampRes",
                                                                     nullptr};
 
+constexpr utils::TypeInfo storage::replication::CreateDatabaseReq::kType{utils::TypeId::REP_CREATE_DATABASE_REQ,
+                                                                         "CreateDatabaseReq", nullptr};
+
+constexpr utils::TypeInfo storage::replication::CreateDatabaseRes::kType{utils::TypeId::REP_CREATE_DATABASE_RES,
+                                                                         "CreateDatabaseRes", nullptr};
+
+constexpr utils::TypeInfo storage::replication::DropDatabaseReq::kType{utils::TypeId::REP_DROP_DATABASE_REQ,
+                                                                       "DropDatabaseReq", nullptr};
+
+constexpr utils::TypeInfo storage::replication::DropDatabaseRes::kType{utils::TypeId::REP_DROP_DATABASE_RES,
+                                                                       "DropDatabaseRes", nullptr};
+
+constexpr utils::TypeInfo storage::replication::SystemRecoveryReq::kType{utils::TypeId::REP_SYSTEM_RECOVERY_REQ,
+                                                                         "SystemRecoveryReq", nullptr};
+
+constexpr utils::TypeInfo storage::replication::SystemRecoveryRes::kType{utils::TypeId::REP_SYSTEM_RECOVERY_RES,
+                                                                         "SystemRecoveryRes", nullptr};
+
 // Autogenerated SLK serialization code
 namespace slk {
 // Serialize code for TimestampRes
 
 void Save(const memgraph::storage::replication::TimestampRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
   memgraph::slk::Save(self.success, builder);
   memgraph::slk::Save(self.current_commit_timestamp, builder);
 }
 
 void Load(memgraph::storage::replication::TimestampRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
   memgraph::slk::Load(&self->success, reader);
   memgraph::slk::Load(&self->current_commit_timestamp, reader);
 }
@@ -114,23 +165,21 @@ void Load(memgraph::storage::replication::TimestampRes *self, memgraph::slk::Rea
 // Serialize code for TimestampReq
 
 void Save(const memgraph::storage::replication::TimestampReq &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
+  memgraph::slk::Save(self.uuid, builder);
 }
 
 void Load(memgraph::storage::replication::TimestampReq *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
 }
 
 // Serialize code for CurrentWalRes
 
 void Save(const memgraph::storage::replication::CurrentWalRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
   memgraph::slk::Save(self.success, builder);
   memgraph::slk::Save(self.current_commit_timestamp, builder);
 }
 
 void Load(memgraph::storage::replication::CurrentWalRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
   memgraph::slk::Load(&self->success, reader);
   memgraph::slk::Load(&self->current_commit_timestamp, reader);
 }
@@ -138,23 +187,21 @@ void Load(memgraph::storage::replication::CurrentWalRes *self, memgraph::slk::Re
 // Serialize code for CurrentWalReq
 
 void Save(const memgraph::storage::replication::CurrentWalReq &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
+  memgraph::slk::Save(self.uuid, builder);
 }
 
 void Load(memgraph::storage::replication::CurrentWalReq *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
 }
 
 // Serialize code for WalFilesRes
 
 void Save(const memgraph::storage::replication::WalFilesRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
   memgraph::slk::Save(self.success, builder);
   memgraph::slk::Save(self.current_commit_timestamp, builder);
 }
 
 void Load(memgraph::storage::replication::WalFilesRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
   memgraph::slk::Load(&self->success, reader);
   memgraph::slk::Load(&self->current_commit_timestamp, reader);
 }
@@ -162,25 +209,23 @@ void Load(memgraph::storage::replication::WalFilesRes *self, memgraph::slk::Read
 // Serialize code for WalFilesReq
 
 void Save(const memgraph::storage::replication::WalFilesReq &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
+  memgraph::slk::Save(self.uuid, builder);
   memgraph::slk::Save(self.file_number, builder);
 }
 
 void Load(memgraph::storage::replication::WalFilesReq *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
   memgraph::slk::Load(&self->file_number, reader);
 }
 
 // Serialize code for SnapshotRes
 
 void Save(const memgraph::storage::replication::SnapshotRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
   memgraph::slk::Save(self.success, builder);
   memgraph::slk::Save(self.current_commit_timestamp, builder);
 }
 
 void Load(memgraph::storage::replication::SnapshotRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
   memgraph::slk::Load(&self->success, reader);
   memgraph::slk::Load(&self->current_commit_timestamp, reader);
 }
@@ -188,24 +233,22 @@ void Load(memgraph::storage::replication::SnapshotRes *self, memgraph::slk::Read
 // Serialize code for SnapshotReq
 
 void Save(const memgraph::storage::replication::SnapshotReq &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
+  memgraph::slk::Save(self.uuid, builder);
 }
 
 void Load(memgraph::storage::replication::SnapshotReq *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
 }
 
 // Serialize code for HeartbeatRes
 
 void Save(const memgraph::storage::replication::HeartbeatRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
   memgraph::slk::Save(self.success, builder);
   memgraph::slk::Save(self.current_commit_timestamp, builder);
   memgraph::slk::Save(self.epoch_id, builder);
 }
 
 void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
   memgraph::slk::Load(&self->success, reader);
   memgraph::slk::Load(&self->current_commit_timestamp, reader);
   memgraph::slk::Load(&self->epoch_id, reader);
@@ -214,13 +257,13 @@ void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Rea
 // Serialize code for HeartbeatReq
 
 void Save(const memgraph::storage::replication::HeartbeatReq &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
+  memgraph::slk::Save(self.uuid, builder);
   memgraph::slk::Save(self.main_commit_timestamp, builder);
   memgraph::slk::Save(self.epoch_id, builder);
 }
 
 void Load(memgraph::storage::replication::HeartbeatReq *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
   memgraph::slk::Load(&self->main_commit_timestamp, reader);
   memgraph::slk::Load(&self->epoch_id, reader);
 }
@@ -228,13 +271,11 @@ void Load(memgraph::storage::replication::HeartbeatReq *self, memgraph::slk::Rea
 // Serialize code for AppendDeltasRes
 
 void Save(const memgraph::storage::replication::AppendDeltasRes &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
   memgraph::slk::Save(self.success, builder);
   memgraph::slk::Save(self.current_commit_timestamp, builder);
 }
 
 void Load(memgraph::storage::replication::AppendDeltasRes *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
   memgraph::slk::Load(&self->success, reader);
   memgraph::slk::Load(&self->current_commit_timestamp, reader);
 }
@@ -242,15 +283,124 @@ void Load(memgraph::storage::replication::AppendDeltasRes *self, memgraph::slk::
 // Serialize code for AppendDeltasReq
 
 void Save(const memgraph::storage::replication::AppendDeltasReq &self, memgraph::slk::Builder *builder) {
-  memgraph::slk::Save(self.db_name, builder);
+  memgraph::slk::Save(self.uuid, builder);
   memgraph::slk::Save(self.previous_commit_timestamp, builder);
   memgraph::slk::Save(self.seq_num, builder);
 }
 
 void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::Reader *reader) {
-  memgraph::slk::Load(&self->db_name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
   memgraph::slk::Load(&self->previous_commit_timestamp, reader);
   memgraph::slk::Load(&self->seq_num, reader);
 }
+
+// Serialize SalientConfig
+
+void Save(const memgraph::storage::SalientConfig &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.name, builder);
+  memgraph::slk::Save(self.uuid, builder);
+  memgraph::slk::Save(utils::EnumToNum<3, uint8_t>(self.storage_mode), builder);
+  memgraph::slk::Save(self.items.properties_on_edges, builder);
+  memgraph::slk::Save(self.items.enable_schema_metadata, builder);
+}
+
+void Load(memgraph::storage::SalientConfig *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->name, reader);
+  memgraph::slk::Load(&self->uuid, reader);
+  uint8_t sm = 0;
+  memgraph::slk::Load(&sm, reader);
+  if (!utils::NumToEnum<3>(sm, self->storage_mode)) {
+    throw SlkReaderException("Unexpected result line:{}!", __LINE__);
+  }
+  memgraph::slk::Load(&self->items.properties_on_edges, reader);
+  memgraph::slk::Load(&self->items.enable_schema_metadata, reader);
+}
+
+// Serialize code for CreateDatabaseReq
+
+void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.epoch_id, builder);
+  memgraph::slk::Save(self.expected_group_timestamp, builder);
+  memgraph::slk::Save(self.new_group_timestamp, builder);
+  memgraph::slk::Save(self.config, builder);
+}
+
+void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->epoch_id, reader);
+  memgraph::slk::Load(&self->expected_group_timestamp, reader);
+  memgraph::slk::Load(&self->new_group_timestamp, reader);
+  memgraph::slk::Load(&self->config, reader);
+}
+
+// Serialize code for CreateDatabaseRes
+
+void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
+}
+
+void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader) {
+  uint8_t res = 0;
+  memgraph::slk::Load(&res, reader);
+  if (!utils::NumToEnum(res, self->result)) {
+    throw SlkReaderException("Unexpected result line:{}!", __LINE__);
+  }
+}
+
+// Serialize code for DropDatabaseReq
+
+void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.epoch_id, builder);
+  memgraph::slk::Save(self.expected_group_timestamp, builder);
+  memgraph::slk::Save(self.new_group_timestamp, builder);
+  memgraph::slk::Save(self.uuid, builder);
+}
+
+void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->epoch_id, reader);
+  memgraph::slk::Load(&self->expected_group_timestamp, reader);
+  memgraph::slk::Load(&self->new_group_timestamp, reader);
+  memgraph::slk::Load(&self->uuid, reader);
+}
+
+// Serialize code for DropDatabaseRes
+
+void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
+}
+
+void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader) {
+  uint8_t res = 0;
+  memgraph::slk::Load(&res, reader);
+  if (!utils::NumToEnum(res, self->result)) {
+    throw SlkReaderException("Unexpected result line:{}!", __LINE__);
+  }
+}
+
+// Serialize code for SystemRecoveryReq
+
+void Save(const memgraph::storage::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(self.forced_group_timestamp, builder);
+  memgraph::slk::Save(self.database_configs, builder);
+}
+
+void Load(memgraph::storage::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader) {
+  memgraph::slk::Load(&self->forced_group_timestamp, reader);
+  memgraph::slk::Load(&self->database_configs, reader);
+}
+
+// Serialize code for SystemRecoveryRes
+
+void Save(const memgraph::storage::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder) {
+  memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
+}
+
+void Load(memgraph::storage::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader) {
+  uint8_t res = 0;
+  memgraph::slk::Load(&res, reader);
+  if (!utils::NumToEnum(res, self->result)) {
+    throw SlkReaderException("Unexpected result line:{}!", __LINE__);
+  }
+}
+
 }  // namespace slk
 }  // namespace memgraph
diff --git a/src/storage/v2/replication/rpc.hpp b/src/storage/v2/replication/rpc.hpp
index 9e2f0b35e..62f8b680c 100644
--- a/src/storage/v2/replication/rpc.hpp
+++ b/src/storage/v2/replication/rpc.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -19,6 +19,9 @@
 #include "rpc/messages.hpp"
 #include "slk/serialization.hpp"
 #include "slk/streams.hpp"
+#include "storage/v2/config.hpp"
+#include "utils/enum.hpp"
+#include "utils/uuid.hpp"
 
 namespace memgraph::storage::replication {
 
@@ -29,10 +32,10 @@ struct AppendDeltasReq {
   static void Load(AppendDeltasReq *self, memgraph::slk::Reader *reader);
   static void Save(const AppendDeltasReq &self, memgraph::slk::Builder *builder);
   AppendDeltasReq() = default;
-  AppendDeltasReq(std::string name, uint64_t previous_commit_timestamp, uint64_t seq_num)
-      : db_name(std::move(name)), previous_commit_timestamp(previous_commit_timestamp), seq_num(seq_num) {}
+  AppendDeltasReq(const utils::UUID &uuid, uint64_t previous_commit_timestamp, uint64_t seq_num)
+      : uuid{uuid}, previous_commit_timestamp(previous_commit_timestamp), seq_num(seq_num) {}
 
-  std::string db_name;
+  utils::UUID uuid;
   uint64_t previous_commit_timestamp;
   uint64_t seq_num;
 };
@@ -44,10 +47,9 @@ struct AppendDeltasRes {
   static void Load(AppendDeltasRes *self, memgraph::slk::Reader *reader);
   static void Save(const AppendDeltasRes &self, memgraph::slk::Builder *builder);
   AppendDeltasRes() = default;
-  AppendDeltasRes(std::string name, bool success, uint64_t current_commit_timestamp)
-      : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
+  AppendDeltasRes(bool success, uint64_t current_commit_timestamp)
+      : success(success), current_commit_timestamp(current_commit_timestamp) {}
 
-  std::string db_name;
   bool success;
   uint64_t current_commit_timestamp;
 };
@@ -61,10 +63,10 @@ struct HeartbeatReq {
   static void Load(HeartbeatReq *self, memgraph::slk::Reader *reader);
   static void Save(const HeartbeatReq &self, memgraph::slk::Builder *builder);
   HeartbeatReq() = default;
-  HeartbeatReq(std::string name, uint64_t main_commit_timestamp, std::string epoch_id)
-      : db_name(std::move(name)), main_commit_timestamp(main_commit_timestamp), epoch_id(std::move(epoch_id)) {}
+  HeartbeatReq(const utils::UUID &uuid, uint64_t main_commit_timestamp, std::string epoch_id)
+      : uuid{uuid}, main_commit_timestamp(main_commit_timestamp), epoch_id(std::move(epoch_id)) {}
 
-  std::string db_name;
+  utils::UUID uuid;
   uint64_t main_commit_timestamp;
   std::string epoch_id;
 };
@@ -76,13 +78,9 @@ struct HeartbeatRes {
   static void Load(HeartbeatRes *self, memgraph::slk::Reader *reader);
   static void Save(const HeartbeatRes &self, memgraph::slk::Builder *builder);
   HeartbeatRes() = default;
-  HeartbeatRes(std::string name, bool success, uint64_t current_commit_timestamp, std::string epoch_id)
-      : db_name(std::move(name)),
-        success(success),
-        current_commit_timestamp(current_commit_timestamp),
-        epoch_id(std::move(epoch_id)) {}
+  HeartbeatRes(bool success, uint64_t current_commit_timestamp, std::string epoch_id)
+      : success(success), current_commit_timestamp(current_commit_timestamp), epoch_id(std::move(epoch_id)) {}
 
-  std::string db_name;
   bool success;
   uint64_t current_commit_timestamp;
   std::string epoch_id;
@@ -97,9 +95,9 @@ struct SnapshotReq {
   static void Load(SnapshotReq *self, memgraph::slk::Reader *reader);
   static void Save(const SnapshotReq &self, memgraph::slk::Builder *builder);
   SnapshotReq() = default;
-  explicit SnapshotReq(std::string name) : db_name(std::move(name)) {}
+  explicit SnapshotReq(const utils::UUID &uuid) : uuid{uuid} {}
 
-  std::string db_name;
+  utils::UUID uuid;
 };
 
 struct SnapshotRes {
@@ -109,10 +107,9 @@ struct SnapshotRes {
   static void Load(SnapshotRes *self, memgraph::slk::Reader *reader);
   static void Save(const SnapshotRes &self, memgraph::slk::Builder *builder);
   SnapshotRes() = default;
-  SnapshotRes(std::string name, bool success, uint64_t current_commit_timestamp)
-      : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
+  SnapshotRes(bool success, uint64_t current_commit_timestamp)
+      : success(success), current_commit_timestamp(current_commit_timestamp) {}
 
-  std::string db_name;
   bool success;
   uint64_t current_commit_timestamp;
 };
@@ -126,9 +123,9 @@ struct WalFilesReq {
   static void Load(WalFilesReq *self, memgraph::slk::Reader *reader);
   static void Save(const WalFilesReq &self, memgraph::slk::Builder *builder);
   WalFilesReq() = default;
-  explicit WalFilesReq(std::string name, uint64_t file_number) : db_name(std::move(name)), file_number(file_number) {}
+  explicit WalFilesReq(const utils::UUID &uuid, uint64_t file_number) : uuid{uuid}, file_number(file_number) {}
 
-  std::string db_name;
+  utils::UUID uuid;
   uint64_t file_number;
 };
 
@@ -139,10 +136,9 @@ struct WalFilesRes {
   static void Load(WalFilesRes *self, memgraph::slk::Reader *reader);
   static void Save(const WalFilesRes &self, memgraph::slk::Builder *builder);
   WalFilesRes() = default;
-  WalFilesRes(std::string name, bool success, uint64_t current_commit_timestamp)
-      : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
+  WalFilesRes(bool success, uint64_t current_commit_timestamp)
+      : success(success), current_commit_timestamp(current_commit_timestamp) {}
 
-  std::string db_name;
   bool success;
   uint64_t current_commit_timestamp;
 };
@@ -156,9 +152,9 @@ struct CurrentWalReq {
   static void Load(CurrentWalReq *self, memgraph::slk::Reader *reader);
   static void Save(const CurrentWalReq &self, memgraph::slk::Builder *builder);
   CurrentWalReq() = default;
-  explicit CurrentWalReq(std::string name) : db_name(std::move(name)) {}
+  explicit CurrentWalReq(const utils::UUID &uuid) : uuid{uuid} {}
 
-  std::string db_name;
+  utils::UUID uuid;
 };
 
 struct CurrentWalRes {
@@ -168,10 +164,9 @@ struct CurrentWalRes {
   static void Load(CurrentWalRes *self, memgraph::slk::Reader *reader);
   static void Save(const CurrentWalRes &self, memgraph::slk::Builder *builder);
   CurrentWalRes() = default;
-  CurrentWalRes(std::string name, bool success, uint64_t current_commit_timestamp)
-      : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
+  CurrentWalRes(bool success, uint64_t current_commit_timestamp)
+      : success(success), current_commit_timestamp(current_commit_timestamp) {}
 
-  std::string db_name;
   bool success;
   uint64_t current_commit_timestamp;
 };
@@ -185,9 +180,9 @@ struct TimestampReq {
   static void Load(TimestampReq *self, memgraph::slk::Reader *reader);
   static void Save(const TimestampReq &self, memgraph::slk::Builder *builder);
   TimestampReq() = default;
-  explicit TimestampReq(std::string name) : db_name(std::move(name)) {}
+  explicit TimestampReq(const utils::UUID &uuid) : uuid{uuid} {}
 
-  std::string db_name;
+  utils::UUID uuid;
 };
 
 struct TimestampRes {
@@ -197,15 +192,117 @@ struct TimestampRes {
   static void Load(TimestampRes *self, memgraph::slk::Reader *reader);
   static void Save(const TimestampRes &self, memgraph::slk::Builder *builder);
   TimestampRes() = default;
-  TimestampRes(std::string name, bool success, uint64_t current_commit_timestamp)
-      : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
+  TimestampRes(bool success, uint64_t current_commit_timestamp)
+      : success(success), current_commit_timestamp(current_commit_timestamp) {}
 
-  std::string db_name;
   bool success;
   uint64_t current_commit_timestamp;
 };
 
 using TimestampRpc = rpc::RequestResponse<TimestampReq, TimestampRes>;
+
+struct CreateDatabaseReq {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader);
+  static void Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder);
+  CreateDatabaseReq() = default;
+  CreateDatabaseReq(std::string epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp,
+                    storage::SalientConfig config)
+      : epoch_id(std::move(epoch_id)),
+        expected_group_timestamp{expected_group_timestamp},
+        new_group_timestamp(new_group_timestamp),
+        config(std::move(config)) {}
+
+  std::string epoch_id;
+  uint64_t expected_group_timestamp;
+  uint64_t new_group_timestamp;
+  storage::SalientConfig config;
+};
+
+struct CreateDatabaseRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
+
+  static void Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader);
+  static void Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder);
+  CreateDatabaseRes() = default;
+  explicit CreateDatabaseRes(Result res) : result(res) {}
+
+  Result result;
+};
+
+using CreateDatabaseRpc = rpc::RequestResponse<CreateDatabaseReq, CreateDatabaseRes>;
+
+struct DropDatabaseReq {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(DropDatabaseReq *self, memgraph::slk::Reader *reader);
+  static void Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder);
+  DropDatabaseReq() = default;
+  DropDatabaseReq(std::string epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp,
+                  const utils::UUID &uuid)
+      : epoch_id(std::move(epoch_id)),
+        expected_group_timestamp{expected_group_timestamp},
+        new_group_timestamp(new_group_timestamp),
+        uuid(uuid) {}
+
+  std::string epoch_id;
+  uint64_t expected_group_timestamp;
+  uint64_t new_group_timestamp;
+  utils::UUID uuid;
+};
+
+struct DropDatabaseRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
+
+  static void Load(DropDatabaseRes *self, memgraph::slk::Reader *reader);
+  static void Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder);
+  DropDatabaseRes() = default;
+  explicit DropDatabaseRes(Result res) : result(res) {}
+
+  Result result;
+};
+
+using DropDatabaseRpc = rpc::RequestResponse<DropDatabaseReq, DropDatabaseRes>;
+
+struct SystemRecoveryReq {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  static void Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader);
+  static void Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder);
+  SystemRecoveryReq() = default;
+  SystemRecoveryReq(uint64_t forced_group_timestamp, std::vector<storage::SalientConfig> database_configs)
+      : forced_group_timestamp{forced_group_timestamp}, database_configs(std::move(database_configs)) {}
+
+  uint64_t forced_group_timestamp;
+  std::vector<storage::SalientConfig> database_configs;
+};
+
+struct SystemRecoveryRes {
+  static const utils::TypeInfo kType;
+  static const utils::TypeInfo &GetTypeInfo() { return kType; }
+
+  enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
+
+  static void Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader);
+  static void Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder);
+  SystemRecoveryRes() = default;
+  explicit SystemRecoveryRes(Result res) : result(res) {}
+
+  Result result;
+};
+
+using SystemRecoveryRpc = rpc::RequestResponse<SystemRecoveryReq, SystemRecoveryRes>;
+
 }  // namespace memgraph::storage::replication
 
 // SLK serialization declarations
@@ -259,4 +356,28 @@ void Save(const memgraph::storage::replication::AppendDeltasReq &self, memgraph:
 
 void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::Reader *reader);
 
+void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::storage::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::storage::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader);
+
+void Save(const memgraph::storage::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder);
+
+void Load(memgraph::storage::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader);
+
 }  // namespace memgraph::slk
diff --git a/src/storage/v2/replication/slk.hpp b/src/storage/v2/replication/slk.hpp
index a202e55af..1d1399cb8 100644
--- a/src/storage/v2/replication/slk.hpp
+++ b/src/storage/v2/replication/slk.hpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -25,17 +25,4 @@ void Load(storage::Gid *gid, slk::Reader *reader);
 void Save(const storage::PropertyValue &value, slk::Builder *builder);
 void Load(storage::PropertyValue *value, slk::Reader *reader);
 
-template <utils::Enum T>
-void Save(const T &enum_value, slk::Builder *builder) {
-  slk::Save(utils::UnderlyingCast(enum_value), builder);
-}
-
-template <utils::Enum T>
-void Load(T *enum_value, slk::Reader *reader) {
-  using UnderlyingType = std::underlying_type_t<T>;
-  UnderlyingType value;
-  slk::Load(&value, reader);
-  *enum_value = static_cast<T>(value);
-}
-
 }  // namespace memgraph::slk
diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp
index 86cc02696..536a504a0 100644
--- a/src/storage/v2/storage.cpp
+++ b/src/storage/v2/storage.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 #include "storage/v2/storage.hpp"
 #include "storage/v2/transaction.hpp"
 #include "storage/v2/vertex_accessor.hpp"
+#include "utils/atomic_memory_block.hpp"
 #include "utils/event_counter.hpp"
 #include "utils/event_histogram.hpp"
 #include "utils/exceptions.hpp"
@@ -43,32 +44,33 @@ Storage::Storage(Config config, StorageMode storage_mode)
       isolation_level_(config.transaction.isolation_level),
       storage_mode_(storage_mode),
       indices_(config, storage_mode),
-      constraints_(config, storage_mode),
-      id_(config.name) {
+      constraints_(config, storage_mode) {
   spdlog::info("Created database with {} storage mode.", StorageModeToString(storage_mode));
 }
 
 Storage::Accessor::Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
-                            StorageMode storage_mode, bool is_main)
+                            StorageMode storage_mode,
+                            memgraph::replication_coordination_glue::ReplicationRole replication_role)
     : storage_(storage),
       // The lock must be acquired before creating the transaction object to
       // prevent freshly created transactions from dangling in an active state
       // during exclusive operations.
       storage_guard_(storage_->main_lock_),
       unique_guard_(storage_->main_lock_, std::defer_lock),
-      transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)),
+      transaction_(storage->CreateTransaction(isolation_level, storage_mode, replication_role)),
       is_transaction_active_(true),
       creation_storage_mode_(storage_mode) {}
 
 Storage::Accessor::Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
-                            StorageMode storage_mode, bool is_main)
+                            StorageMode storage_mode,
+                            memgraph::replication_coordination_glue::ReplicationRole replication_role)
     : storage_(storage),
       // The lock must be acquired before creating the transaction object to
       // prevent freshly created transactions from dangling in an active state
       // during exclusive operations.
       storage_guard_(storage_->main_lock_, std::defer_lock),
       unique_guard_(storage_->main_lock_),
-      transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)),
+      transaction_(storage->CreateTransaction(isolation_level, storage_mode, replication_role)),
       is_transaction_active_(true),
       creation_storage_mode_(storage_mode) {}
 
@@ -318,7 +320,7 @@ EdgeInfoForDeletion Storage::Accessor::PrepareDeletableEdges(const std::unordere
     const auto &[edge_type, opposing_vertex, edge] = item;
     if (!vertices.contains(opposing_vertex)) {
       partial_delete_vertices.insert(opposing_vertex);
-      auto const edge_gid = storage_->config_.items.properties_on_edges ? edge.ptr->gid : edge.gid;
+      auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge.ptr->gid : edge.gid;
       edge_ids.insert(edge_gid);
     }
   };
@@ -380,7 +382,7 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::ClearEdgesOn
 
       /// TODO: (andi) Again here, no need to lock the edge if using on disk storage.
       std::unique_lock<utils::RWSpinLock> guard;
-      if (storage_->config_.items.properties_on_edges) {
+      if (storage_->config_.salient.items.properties_on_edges) {
         auto edge_ptr = edge_ref.ptr;
         guard = std::unique_lock{edge_ptr->lock};
 
@@ -390,22 +392,29 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::ClearEdgesOn
       if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR;
       MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!");
 
-      attached_edges_to_vertex->pop_back();
-      if (storage_->config_.items.properties_on_edges) {
-        auto *edge_ptr = edge_ref.ptr;
-        MarkEdgeAsDeleted(edge_ptr);
-      }
+      // MarkEdgeAsDeleted allocates additional memory
+      // and CreateAndLinkDelta needs memory
+      utils::AtomicMemoryBlock atomic_memory_block{[&attached_edges_to_vertex, &deleted_edge_ids, &reverse_vertex_order,
+                                                    &vertex_ptr, &deleted_edges, deletion_delta = deletion_delta,
+                                                    edge_type = edge_type, opposing_vertex = opposing_vertex,
+                                                    edge_ref = edge_ref, this]() {
+        attached_edges_to_vertex->pop_back();
+        if (this->storage_->config_.salient.items.properties_on_edges) {
+          auto *edge_ptr = edge_ref.ptr;
+          MarkEdgeAsDeleted(edge_ptr);
+        }
 
-      auto const edge_gid = storage_->config_.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid;
-      auto const [_, was_inserted] = deleted_edge_ids.insert(edge_gid);
-      bool const edge_cleared_from_both_directions = !was_inserted;
-      if (edge_cleared_from_both_directions) {
-        auto *from_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex;
-        auto *to_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr;
-        deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true);
-      }
-
-      CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref);
+        auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid;
+        auto const [_, was_inserted] = deleted_edge_ids.insert(edge_gid);
+        bool const edge_cleared_from_both_directions = !was_inserted;
+        if (edge_cleared_from_both_directions) {
+          auto *from_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex;
+          auto *to_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr;
+          deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true);
+        }
+        CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref);
+      }};
+      std::invoke(atomic_memory_block);
     }
 
     return std::make_optional<ReturnType>();
@@ -445,35 +454,41 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::DetachRemain
     auto mid = std::partition(
         edges_attached_to_vertex->begin(), edges_attached_to_vertex->end(), [this, &set_for_erasure](auto &edge) {
           auto const &[edge_type, opposing_vertex, edge_ref] = edge;
-          auto const edge_gid = storage_->config_.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid;
+          auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid;
           return !set_for_erasure.contains(edge_gid);
         });
 
-    for (auto it = mid; it != edges_attached_to_vertex->end(); it++) {
-      auto const &[edge_type, opposing_vertex, edge_ref] = *it;
-      std::unique_lock<utils::RWSpinLock> guard;
-      if (storage_->config_.items.properties_on_edges) {
-        auto edge_ptr = edge_ref.ptr;
-        guard = std::unique_lock{edge_ptr->lock};
-        // this can happen only if we marked edges for deletion with no nodes,
-        // so the method detaching nodes will not do anything
-        MarkEdgeAsDeleted(edge_ptr);
+    // Creating deltas and erasing edge only at the end -> we might have incomplete state as
+    // delta might cause OOM, so we don't remove edges from edges_attached_to_vertex
+    utils::AtomicMemoryBlock atomic_memory_block{[&mid, &edges_attached_to_vertex, &deleted_edges,
+                                                  &partially_detached_edge_ids, this, vertex_ptr, deletion_delta,
+                                                  reverse_vertex_order]() {
+      for (auto it = mid; it != edges_attached_to_vertex->end(); it++) {
+        auto const &[edge_type, opposing_vertex, edge_ref] = *it;
+        std::unique_lock<utils::RWSpinLock> guard;
+        if (storage_->config_.salient.items.properties_on_edges) {
+          auto edge_ptr = edge_ref.ptr;
+          guard = std::unique_lock{edge_ptr->lock};
+          // this can happen only if we marked edges for deletion with no nodes,
+          // so the method detaching nodes will not do anything
+          MarkEdgeAsDeleted(edge_ptr);
+        }
+
+        CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref);
+
+        auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid;
+        auto const [_, was_inserted] = partially_detached_edge_ids.insert(edge_gid);
+        bool const edge_cleared_from_both_directions = !was_inserted;
+        if (edge_cleared_from_both_directions) {
+          auto *from_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr;
+          auto *to_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex;
+          deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true);
+        }
       }
+      edges_attached_to_vertex->erase(mid, edges_attached_to_vertex->end());
+    }};
 
-      CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref);
-
-      auto const edge_gid = storage_->config_.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid;
-      auto const [_, was_inserted] = partially_detached_edge_ids.insert(edge_gid);
-      bool const edge_cleared_from_both_directions = !was_inserted;
-      if (edge_cleared_from_both_directions) {
-        auto *from_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr;
-        auto *to_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex;
-        deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true);
-      }
-    }
-
-    edges_attached_to_vertex->erase(mid, edges_attached_to_vertex->end());
-
+    std::invoke(atomic_memory_block);
     return std::make_optional<ReturnType>();
   };
 
diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp
index bf1973bcd..a096f27fd 100644
--- a/src/storage/v2/storage.hpp
+++ b/src/storage/v2/storage.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,6 +12,8 @@
 #pragma once
 
 #include <chrono>
+#include <functional>
+#include <optional>
 #include <semaphore>
 #include <span>
 #include <thread>
@@ -24,6 +26,7 @@
 #include "storage/v2/all_vertices_iterable.hpp"
 #include "storage/v2/commit_log.hpp"
 #include "storage/v2/config.hpp"
+#include "storage/v2/database_access.hpp"
 #include "storage/v2/durability/paths.hpp"
 #include "storage/v2/durability/wal.hpp"
 #include "storage/v2/edge_accessor.hpp"
@@ -52,7 +55,6 @@ extern const Event ActiveLabelPropertyIndices;
 }  // namespace memgraph::metrics
 
 namespace memgraph::storage {
-
 struct Transaction;
 class EdgeAccessor;
 
@@ -108,6 +110,15 @@ struct EdgeInfoForDeletion {
   std::unordered_set<Vertex *> partial_dest_vertices{};
 };
 
+struct CommitReplArgs {
+  // REPLICA on recipt of Deltas will have a desired commit timestamp
+  std::optional<uint64_t> desired_commit_timestamp = std::nullopt;
+
+  bool is_main = true;
+
+  bool IsMain() { return is_main; }
+};
+
 class Storage {
   friend class ReplicationServer;
   friend class ReplicationStorageClient;
@@ -122,7 +133,9 @@ class Storage {
 
   virtual ~Storage() = default;
 
-  const std::string &id() const { return id_; }
+  const std::string &name() const { return config_.salient.name; }
+
+  const utils::UUID &uuid() const { return config_.salient.uuid; }
 
   class Accessor {
    public:
@@ -132,9 +145,9 @@ class Storage {
     } unique_access;
 
     Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode,
-             bool is_main = true);
+             memgraph::replication_coordination_glue::ReplicationRole replication_role);
     Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode,
-             bool is_main = true);
+             memgraph::replication_coordination_glue::ReplicationRole replication_role);
     Accessor(const Accessor &) = delete;
     Accessor &operator=(const Accessor &) = delete;
     Accessor &operator=(Accessor &&other) = delete;
@@ -216,8 +229,8 @@ class Storage {
     virtual ConstraintsInfo ListAllConstraints() const = 0;
 
     // NOLINTNEXTLINE(google-default-arguments)
-    virtual utils::BasicResult<StorageManipulationError, void> Commit(
-        std::optional<uint64_t> desired_commit_timestamp = {}, bool is_main = true) = 0;
+    virtual utils::BasicResult<StorageManipulationError, void> Commit(CommitReplArgs reparg = {},
+                                                                      DatabaseAccessProtector db_acc = {}) = 0;
 
     virtual void Abort() = 0;
 
@@ -241,7 +254,7 @@ class Storage {
 
     StorageMode GetCreationStorageMode() const noexcept;
 
-    const std::string &id() const { return storage_->id(); }
+    const std::string &id() const { return storage_->name(); }
 
     std::vector<LabelId> ListAllPossiblyPresentVertexLabels() const;
 
@@ -315,19 +328,18 @@ class Storage {
 
   void FreeMemory() { FreeMemory({}); }
 
-  virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level, bool is_main) = 0;
-  std::unique_ptr<Accessor> Access(bool is_main = true) { return Access(std::optional<IsolationLevel>{}, is_main); }
-  std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) {
-    return Access(std::move(override_isolation_level), true);
+  virtual std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role,
+                                           std::optional<IsolationLevel> override_isolation_level) = 0;
+
+  std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role) {
+    return Access(replication_role, {});
   }
 
-  virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
-                                                 bool is_main) = 0;
-  std::unique_ptr<Accessor> UniqueAccess(bool is_main = true) {
-    return UniqueAccess(std::optional<IsolationLevel>{}, is_main);
-  }
-  std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) {
-    return UniqueAccess(std::move(override_isolation_level), true);
+  virtual std::unique_ptr<Accessor> UniqueAccess(
+      memgraph::replication_coordination_glue::ReplicationRole replication_role,
+      std::optional<IsolationLevel> override_isolation_level) = 0;
+  std::unique_ptr<Accessor> UniqueAccess(memgraph::replication_coordination_glue::ReplicationRole replication_role) {
+    return UniqueAccess(replication_role, {});
   }
 
   enum class SetIsolationLevelError : uint8_t { DisabledForAnalyticalMode };
@@ -345,21 +357,11 @@ class Storage {
     return GetBaseInfo(force_dir);
   }
 
-  virtual StorageInfo GetInfo(bool force_directory) = 0;
-  StorageInfo GetInfo() {
-#if MG_ENTERPRISE
-    const bool force_dir = false;
-#else
-    const bool force_dir = true;  //!< Use the configured directory (multi-tenancy reroutes to another dir)
-#endif
-    return GetInfo(force_dir);
-  }
+  virtual StorageInfo GetInfo(bool force_directory,
+                              memgraph::replication_coordination_glue::ReplicationRole replication_role) = 0;
 
-  Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
-    return CreateTransaction(isolation_level, storage_mode, true);
-  }
-
-  virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) = 0;
+  virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode,
+                                        memgraph::replication_coordination_glue::ReplicationRole replication_role) = 0;
 
   virtual void PrepareForNewEpoch() = 0;
 
@@ -412,7 +414,6 @@ class Storage {
 
   std::atomic<uint64_t> vertex_id_{0};
   std::atomic<uint64_t> edge_id_{0};
-  const std::string id_;  //!< High-level assigned ID
 };
 
 }  // namespace memgraph::storage
diff --git a/src/storage/v2/storage_mode.hpp b/src/storage/v2/storage_mode.hpp
index c02d3c177..f4a133f38 100644
--- a/src/storage/v2/storage_mode.hpp
+++ b/src/storage/v2/storage_mode.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,14 +11,19 @@
 
 #pragma once
 
+#include <array>
 #include <cstdint>
 #include <string_view>
-
 namespace memgraph::storage {
 
 enum class StorageMode : std::uint8_t { IN_MEMORY_ANALYTICAL, IN_MEMORY_TRANSACTIONAL, ON_DISK_TRANSACTIONAL };
 
-bool IsTransactional(const StorageMode storage_mode) noexcept;
+inline constexpr std::array storage_mode_mappings{
+    std::pair{std::string_view{"IN_MEMORY_TRANSACTIONAL"}, memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL},
+    std::pair{std::string_view{"IN_MEMORY_ANALYTICAL"}, memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL},
+    std::pair{std::string_view{"ON_DISK_TRANSACTIONAL"}, memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL}};
+
+bool IsTransactional(StorageMode storage_mode) noexcept;
 
 std::string_view StorageModeToString(memgraph::storage::StorageMode storage_mode);
 
diff --git a/src/storage/v2/vertex_accessor.cpp b/src/storage/v2/vertex_accessor.cpp
index ff5881563..ff5062444 100644
--- a/src/storage/v2/vertex_accessor.cpp
+++ b/src/storage/v2/vertex_accessor.cpp
@@ -26,6 +26,7 @@
 #include "storage/v2/storage.hpp"
 #include "storage/v2/vertex_info_cache.hpp"
 #include "storage/v2/vertex_info_helpers.hpp"
+#include "utils/atomic_memory_block.hpp"
 #include "utils/logging.hpp"
 #include "utils/memory_tracker.hpp"
 #include "utils/variant_helpers.hpp"
@@ -107,10 +108,13 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
   if (vertex_->deleted) return Error::DELETED_OBJECT;
   if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false;
 
-  CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
-  vertex_->labels.push_back(label);
+  utils::AtomicMemoryBlock atomic_memory_block{[transaction = transaction_, vertex = vertex_, &label]() {
+    CreateAndLinkDelta(transaction, vertex, Delta::RemoveLabelTag(), label);
+    vertex->labels.push_back(label);
+  }};
+  std::invoke(atomic_memory_block);
 
-  if (storage_->config_.items.enable_schema_metadata) {
+  if (storage_->config_.salient.items.enable_schema_metadata) {
     storage_->stored_node_labels_.try_insert(label);
   }
 
@@ -136,9 +140,12 @@ Result<bool> VertexAccessor::RemoveLabel(LabelId label) {
   auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label);
   if (it == vertex_->labels.end()) return false;
 
-  CreateAndLinkDelta(transaction_, vertex_, Delta::AddLabelTag(), label);
-  *it = vertex_->labels.back();
-  vertex_->labels.pop_back();
+  utils::AtomicMemoryBlock atomic_memory_block{[transaction = transaction_, vertex = vertex_, &label, &it]() {
+    CreateAndLinkDelta(transaction, vertex, Delta::AddLabelTag(), label);
+    *it = vertex->labels.back();
+    vertex->labels.pop_back();
+  }};
+  std::invoke(atomic_memory_block);
 
   /// TODO: some by pointers, some by reference => not good, make it better
   storage_->constraints_.unique_constraints_->UpdateOnRemoveLabel(label, *vertex_, transaction_->start_timestamp);
@@ -262,8 +269,12 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
   // "modify in-place". Additionally, the created delta will make other
   // transactions get a SERIALIZATION_ERROR.
 
-  CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value);
-  vertex_->properties.SetProperty(property, value);
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [transaction = transaction_, vertex = vertex_, &value, &property, &current_value]() {
+        CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, current_value);
+        vertex->properties.SetProperty(property, value);
+      }};
+  std::invoke(atomic_memory_block);
 
   if (!value.IsNull()) {
     transaction_->constraint_verification_info.AddedProperty(vertex_);
@@ -287,20 +298,28 @@ Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId,
   if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
 
   if (vertex_->deleted) return Error::DELETED_OBJECT;
+  bool result{false};
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [&result, &properties, storage = storage_, transaction = transaction_, vertex = vertex_]() {
+        if (!vertex->properties.InitProperties(properties)) {
+          result = false;
+          return;
+        }
+        for (const auto &[property, value] : properties) {
+          CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, PropertyValue());
+          storage->indices_.UpdateOnSetProperty(property, value, vertex, *transaction);
+          transaction->manyDeltasCache.Invalidate(vertex, property);
+          if (!value.IsNull()) {
+            transaction->constraint_verification_info.AddedProperty(vertex);
+          } else {
+            transaction->constraint_verification_info.RemovedProperty(vertex);
+          }
+        }
+        result = true;
+      }};
+  std::invoke(atomic_memory_block);
 
-  if (!vertex_->properties.InitProperties(properties)) return false;
-  for (const auto &[property, value] : properties) {
-    CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, PropertyValue());
-    storage_->indices_.UpdateOnSetProperty(property, value, vertex_, *transaction_);
-    transaction_->manyDeltasCache.Invalidate(vertex_, property);
-    if (!value.IsNull()) {
-      transaction_->constraint_verification_info.AddedProperty(vertex_);
-    } else {
-      transaction_->constraint_verification_info.RemovedProperty(vertex_);
-    }
-  }
-
-  return true;
+  return result;
 }
 
 Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> VertexAccessor::UpdateProperties(
@@ -316,20 +335,28 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> Vertex
 
   if (vertex_->deleted) return Error::DELETED_OBJECT;
 
-  auto id_old_new_change = vertex_->properties.UpdateProperties(properties);
+  using ReturnType = decltype(vertex_->properties.UpdateProperties(properties));
+  std::optional<ReturnType> id_old_new_change;
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [storage = storage_, transaction = transaction_, vertex = vertex_, &properties, &id_old_new_change]() {
+        id_old_new_change.emplace(vertex->properties.UpdateProperties(properties));
+        if (!id_old_new_change.has_value()) {
+          return;
+        }
+        for (auto &[id, old_value, new_value] : *id_old_new_change) {
+          storage->indices_.UpdateOnSetProperty(id, new_value, vertex, *transaction);
+          CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), id, std::move(old_value));
+          transaction->manyDeltasCache.Invalidate(vertex, id);
+          if (!new_value.IsNull()) {
+            transaction->constraint_verification_info.AddedProperty(vertex);
+          } else {
+            transaction->constraint_verification_info.RemovedProperty(vertex);
+          }
+        }
+      }};
+  std::invoke(atomic_memory_block);
 
-  for (auto &[id, old_value, new_value] : id_old_new_change) {
-    storage_->indices_.UpdateOnSetProperty(id, new_value, vertex_, *transaction_);
-    CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), id, std::move(old_value));
-    transaction_->manyDeltasCache.Invalidate(vertex_, id);
-    if (!new_value.IsNull()) {
-      transaction_->constraint_verification_info.AddedProperty(vertex_);
-    } else {
-      transaction_->constraint_verification_info.RemovedProperty(vertex_);
-    }
-  }
-
-  return id_old_new_change;
+  return id_old_new_change.has_value() ? std::move(id_old_new_change.value()) : ReturnType{};
 }
 
 Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
@@ -342,17 +369,25 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
 
   if (vertex_->deleted) return Error::DELETED_OBJECT;
 
-  auto properties = vertex_->properties.Properties();
-  for (const auto &[property, value] : properties) {
-    CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, value);
-    storage_->indices_.UpdateOnSetProperty(property, PropertyValue(), vertex_, *transaction_);
-    transaction_->constraint_verification_info.RemovedProperty(vertex_);
-    transaction_->manyDeltasCache.Invalidate(vertex_, property);
-  }
+  using ReturnType = decltype(vertex_->properties.Properties());
+  std::optional<ReturnType> properties;
+  utils::AtomicMemoryBlock atomic_memory_block{
+      [storage = storage_, transaction = transaction_, vertex = vertex_, &properties]() {
+        properties.emplace(vertex->properties.Properties());
+        if (!properties.has_value()) {
+          return;
+        }
+        for (const auto &[property, value] : *properties) {
+          CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, value);
+          storage->indices_.UpdateOnSetProperty(property, PropertyValue(), vertex, *transaction);
+          transaction->constraint_verification_info.RemovedProperty(vertex);
+          transaction->manyDeltasCache.Invalidate(vertex, property);
+        }
+        vertex->properties.ClearProperties();
+      }};
+  std::invoke(atomic_memory_block);
 
-  vertex_->properties.ClearProperties();
-
-  return std::move(properties);
+  return properties.has_value() ? std::move(properties.value()) : ReturnType{};
 }
 
 Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view) const {
diff --git a/src/telemetry/telemetry.cpp b/src/telemetry/telemetry.cpp
index 1635dda99..e5e779f31 100644
--- a/src/telemetry/telemetry.cpp
+++ b/src/telemetry/telemetry.cpp
@@ -110,7 +110,13 @@ void Telemetry::CollectData(const std::string &event) {
   {
     std::lock_guard<std::mutex> guard(lock_);
     for (auto &collector : collectors_) {
-      data[collector.first] = collector.second();
+      try {
+        data[collector.first] = collector.second();
+      } catch (std::exception &e) {
+        spdlog::warn(fmt::format(
+            "Unknwon exception occured on in telemetry server {}, please contact support on https://memgr.ph/unknown ",
+            e.what()));
+      }
     }
   }
   if (event == "") {
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 276927725..bac3e78f3 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -24,8 +24,8 @@ find_package(Threads REQUIRED)
 
 add_library(mg-utils STATIC ${utils_src_files})
 add_library(mg::utils ALIAS mg-utils)
-target_link_libraries(mg-utils PUBLIC Boost::headers fmt::fmt spdlog::spdlog)
-target_link_libraries(mg-utils PRIVATE librdtsc stdc++fs Threads::Threads gflags json uuid rt)
+target_link_libraries(mg-utils PUBLIC Boost::headers fmt::fmt spdlog::spdlog json)
+target_link_libraries(mg-utils PRIVATE librdtsc stdc++fs Threads::Threads gflags uuid rt)
 
 set(settings_src_files
     settings.cpp)
diff --git a/src/utils/algorithm.hpp b/src/utils/algorithm.hpp
index cac7ccc9b..fc34e9a4d 100644
--- a/src/utils/algorithm.hpp
+++ b/src/utils/algorithm.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -68,7 +68,7 @@ inline std::string IterableToString(const TIterable &iterable, const std::string
  */
 template <typename TIterable>
 inline std::string IterableToString(const TIterable &iterable, const std::string_view delim = ", ") {
-  return IterableToString(iterable, delim, [](const auto &item) { return item; });
+  return IterableToString(iterable, delim, std::identity{});
 }
 
 /**
@@ -216,8 +216,7 @@ TCollection Reversed(const TCollection &collection, const TAllocator &alloc) {
 template <typename TIterator>
 class Iterable {
  public:
-  Iterable(TIterator &&begin, TIterator &&end)
-      : begin_(std::forward<TIterator>(begin)), end_(std::forward<TIterator>(end)) {}
+  Iterable(TIterator &&begin, TIterator &&end) : begin_(std::move(begin)), end_(std::move(end)) {}
 
   auto begin() { return begin_; };
   auto end() { return end_; };
diff --git a/src/utils/atomic_memory_block.hpp b/src/utils/atomic_memory_block.hpp
new file mode 100644
index 000000000..c15424549
--- /dev/null
+++ b/src/utils/atomic_memory_block.hpp
@@ -0,0 +1,44 @@
+// Copyright 2023 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <functional>
+#include "utils/memory_tracker.hpp"
+
+namespace memgraph::utils {
+
+// Calls a function with out of memory exception blocker, checks memory allocation after block execution.
+// Use it in case you need block which will be executed atomically considering memory execution
+// but will check after block is executed if OOM exceptions needs to be thrown
+template <typename Callable>
+class [[nodiscard]] AtomicMemoryBlock {
+ public:
+  explicit AtomicMemoryBlock(Callable &&function) : function_{std::forward<Callable>(function)} {}
+  AtomicMemoryBlock(AtomicMemoryBlock const &) = delete;
+  AtomicMemoryBlock(AtomicMemoryBlock &&) = delete;
+  AtomicMemoryBlock &operator=(AtomicMemoryBlock const &) = delete;
+  AtomicMemoryBlock &operator=(AtomicMemoryBlock &&) = delete;
+  ~AtomicMemoryBlock() = default;
+
+  void operator()() {
+    {
+      utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_blocker;
+      function_();
+    }
+    total_memory_tracker.DoCheck();
+  }
+
+ private:
+  Callable function_;
+};
+
+}  // namespace memgraph::utils
diff --git a/src/utils/counter.hpp b/src/utils/counter.hpp
new file mode 100644
index 000000000..0d9aabca8
--- /dev/null
+++ b/src/utils/counter.hpp
@@ -0,0 +1,29 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <cstdint>
+
+namespace memgraph::utils {
+
+/// A resetable counter, every Nth call returns true
+template <std::size_t N>
+auto ResettableCounter() {
+  return [counter = N]() mutable {
+    --counter;
+    if (counter != 0) return false;
+    counter = N;
+    return true;
+  };
+}
+
+}  // namespace memgraph::utils
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
index 505802088..50a6d4621 100644
--- a/src/utils/enum.hpp
+++ b/src/utils/enum.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -54,4 +54,46 @@ std::optional<Enum> StringToEnum(const auto &value, const auto &mappings) {
 
   return mapping_iter->second;
 }
+
+// Tries to convert a enum into string, which would then contain a value if the conversion
+// has been successful.
+template <typename Enum>
+auto EnumToString(const auto &value, const auto &mappings) -> std::optional<std::string_view> {
+  const auto mapping_iter =
+      std::find_if(mappings.begin(), mappings.end(), [&](const auto &mapping) { return mapping.second == value; });
+  if (mapping_iter == mappings.cend()) {
+    return std::nullopt;
+  }
+  return mapping_iter->first;
+}
+
+template <typename T, typename Enum>
+requires std::integral<T>
+inline T EnumToNum(Enum res) {
+  static_assert(std::numeric_limits<T>::max() >= static_cast<size_t>(Enum::N));
+  return static_cast<T>(res);
+}
+
+template <typename T, typename Enum>
+requires std::integral<T>
+inline bool NumToEnum(T input, Enum &res) {
+  if (input >= EnumToNum<T>(Enum::N)) return false;
+  res = static_cast<Enum>(input);
+  return true;
+}
+
+template <size_t Num, typename T, typename Enum>
+requires std::integral<T>
+inline T EnumToNum(Enum res) {
+  static_assert(std::numeric_limits<T>::max() >= Num);
+  return static_cast<T>(res);
+}
+
+template <size_t Num, typename T, typename Enum>
+requires std::integral<T>
+inline bool NumToEnum(T input, Enum &res) {
+  if (input >= Num) return false;
+  res = static_cast<Enum>(input);
+  return true;
+}
 }  // namespace memgraph::utils
diff --git a/src/utils/file.cpp b/src/utils/file.cpp
index de6590620..73ea424ac 100644
--- a/src/utils/file.cpp
+++ b/src/utils/file.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -504,7 +504,7 @@ void OutputFile::Close() noexcept {
 void OutputFile::FlushBuffer(bool force_flush) {
   MG_ASSERT(IsOpen(), "Flushing an unopend file.");
 
-  if (!force_flush && buffer_position_.load() < kFileBufferSize) return;
+  if (!force_flush && buffer_position_ < kFileBufferSize) return;
 
   std::unique_lock flush_guard(flush_lock_);
   FlushBufferInternal();
diff --git a/src/utils/gatekeeper.hpp b/src/utils/gatekeeper.hpp
index 21dad2543..862cad982 100644
--- a/src/utils/gatekeeper.hpp
+++ b/src/utils/gatekeeper.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -78,21 +78,33 @@ struct EvalResult {
 template <typename Func, typename T>
 EvalResult(run_t, Func &&, T &) -> EvalResult<std::invoke_result_t<Func, T &>>;
 
+template <typename T>
+struct GKInternals {
+  template <typename... Args>
+  explicit GKInternals(Args &&...args) : value_{std::in_place, std::forward<Args>(args)...} {}
+
+  std::optional<T> value_;
+  uint64_t count_ = 0;
+  std::atomic_bool is_deleting = false;
+  std::mutex mutex_;  // TODO change to something cheaper?
+  std::condition_variable cv_;
+};
+
 template <typename T>
 struct Gatekeeper {
   template <typename... Args>
-  explicit Gatekeeper(Args &&...args) : value_{std::in_place, std::forward<Args>(args)...} {}
+  explicit Gatekeeper(Args &&...args) : pimpl_(std::make_unique<GKInternals<T>>(std::forward<Args>(args)...)) {}
 
   Gatekeeper(Gatekeeper const &) = delete;
-  Gatekeeper(Gatekeeper &&) noexcept = delete;
+  Gatekeeper(Gatekeeper &&) noexcept = default;
   Gatekeeper &operator=(Gatekeeper const &) = delete;
-  Gatekeeper &operator=(Gatekeeper &&) = delete;
+  Gatekeeper &operator=(Gatekeeper &&) noexcept = default;
 
   struct Accessor {
     friend Gatekeeper;
 
    private:
-    explicit Accessor(Gatekeeper *owner) : owner_{owner} { ++owner_->count_; }
+    explicit Accessor(Gatekeeper *owner) : owner_{owner->pimpl_.get()} { ++owner_->count_; }
 
    public:
     Accessor(Accessor const &other) : owner_{other.owner_} {
@@ -139,6 +151,14 @@ struct Gatekeeper {
       return *this;
     }
 
+    [[nodiscard]] bool is_deleting() const { return owner_->is_deleting; }
+
+    void prepare_for_deletion() {
+      if (owner_) {
+        owner_->is_deleting = true;
+      }
+    }
+
     ~Accessor() { reset(); }
 
     auto get() -> T * { return std::addressof(*owner_->value_); }
@@ -159,18 +179,26 @@ struct Gatekeeper {
     }
 
     // Completely invalidated the accessor if return true
-    [[nodiscard]] bool try_delete(std::chrono::milliseconds timeout = std::chrono::milliseconds(100)) {
+    template <typename Func = decltype([](T &) { return true; })>
+    [[nodiscard]] bool try_delete(std::chrono::milliseconds timeout = std::chrono::milliseconds(100),
+                                  Func &&predicate = {}) {
       // Prevent new access
       auto guard = std::unique_lock{owner_->mutex_};
       if (!owner_->cv_.wait_for(guard, timeout, [this] { return owner_->count_ == 1; })) {
         return false;
       }
-      // Delete value
+      // Already deleted
+      if (owner_->value_ == std::nullopt) return true;
+      // Delete value if ok
+      if (!predicate(*owner_->value_)) return false;
       owner_->value_ = std::nullopt;
       return true;
     }
 
-    explicit operator bool() const { return owner_ != nullptr; }
+    explicit operator bool() const {
+      return owner_ != nullptr         // we have access
+             && !owner_->is_deleting;  // AND we are allowed to use it
+    }
 
     void reset() {
       if (owner_) {
@@ -186,28 +214,27 @@ struct Gatekeeper {
     friend bool operator==(Accessor const &lhs, Accessor const &rhs) { return lhs.owner_ == rhs.owner_; }
 
    private:
-    Gatekeeper *owner_ = nullptr;
+    GKInternals<T> *owner_ = nullptr;
   };
 
   std::optional<Accessor> access() {
-    auto guard = std::unique_lock{mutex_};
-    if (value_) {
+    auto guard = std::unique_lock{pimpl_->mutex_};
+    if (pimpl_->value_) {
       return Accessor{this};
     }
     return std::nullopt;
   }
 
   ~Gatekeeper() {
+    if (!pimpl_) return;  // Moved out, nothing to do
+    pimpl_->is_deleting = true;
     // wait for count to drain to 0
-    auto lock = std::unique_lock{mutex_};
-    cv_.wait(lock, [this] { return count_ == 0; });
+    auto lock = std::unique_lock{pimpl_->mutex_};
+    pimpl_->cv_.wait(lock, [this] { return pimpl_->count_ == 0; });
   }
 
  private:
-  std::optional<T> value_;
-  uint64_t count_ = 0;
-  std::mutex mutex_;  // TODO change to something cheaper?
-  std::condition_variable cv_;
+  std::unique_ptr<GKInternals<T>> pimpl_;
 };
 
 }  // namespace memgraph::utils
diff --git a/src/utils/memory_tracker.cpp b/src/utils/memory_tracker.cpp
index 029223b71..7dfd88416 100644
--- a/src/utils/memory_tracker.cpp
+++ b/src/utils/memory_tracker.cpp
@@ -124,6 +124,19 @@ void MemoryTracker::Alloc(const int64_t size) {
   UpdatePeak(will_be);
 }
 
+void MemoryTracker::DoCheck() {
+  const auto current_hard_limit = hard_limit_.load(std::memory_order_relaxed);
+  const auto current_amount = amount_.load(std::memory_order_relaxed);
+  if (current_hard_limit && current_amount > current_hard_limit && MemoryTrackerCanThrow()) [[unlikely]] {
+    MemoryTracker::OutOfMemoryExceptionBlocker exception_blocker;
+    throw OutOfMemoryException(
+        fmt::format("Memory limit exceeded! Current "
+                    "use is {}, while the maximum allowed size for allocation is set to {}.",
+                    GetReadableSize(static_cast<double>(current_amount)),
+                    GetReadableSize(static_cast<double>(current_hard_limit))));
+  }
+}
+
 void MemoryTracker::Free(const int64_t size) { amount_.fetch_sub(size, std::memory_order_relaxed); }
 
 }  // namespace memgraph::utils
diff --git a/src/utils/memory_tracker.hpp b/src/utils/memory_tracker.hpp
index 0da888161..a6d7221ff 100644
--- a/src/utils/memory_tracker.hpp
+++ b/src/utils/memory_tracker.hpp
@@ -49,6 +49,7 @@ class MemoryTracker final {
 
   void Alloc(int64_t size);
   void Free(int64_t size);
+  void DoCheck();
 
   auto Amount() const { return amount_.load(std::memory_order_relaxed); }
 
diff --git a/src/utils/on_scope_exit.hpp b/src/utils/on_scope_exit.hpp
index 70aec52b6..1c0b34fef 100644
--- a/src/utils/on_scope_exit.hpp
+++ b/src/utils/on_scope_exit.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,6 +11,7 @@
 
 #pragma once
 
+#include <concepts>
 #include <functional>
 
 namespace memgraph::utils {
@@ -37,7 +38,9 @@ namespace memgraph::utils {
 template <typename Callable>
 class [[nodiscard]] OnScopeExit {
  public:
-  explicit OnScopeExit(Callable &&function) : function_{std::forward<Callable>(function)}, doCall_{true} {}
+  template <typename U>
+  requires std::constructible_from<Callable, U>
+  explicit OnScopeExit(U &&function) : function_{std::forward<U>(function)}, doCall_{true} {}
   OnScopeExit(OnScopeExit const &) = delete;
   OnScopeExit(OnScopeExit &&) = delete;
   OnScopeExit &operator=(OnScopeExit const &) = delete;
diff --git a/src/utils/resource_lock.hpp b/src/utils/resource_lock.hpp
index 7d6be2685..7a3ef9444 100644
--- a/src/utils/resource_lock.hpp
+++ b/src/utils/resource_lock.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -48,6 +48,15 @@ struct ResourceLock {
     }
     return false;
   }
+
+  template <class Rep, class Period>
+  bool try_lock_for(const std::chrono::duration<Rep, Period> &timeout_duration) {
+    auto lock = std::unique_lock{mtx};
+    if (!cv.wait_for(lock, timeout_duration, [this] { return state == UNLOCKED; })) return false;
+    state = UNIQUE;
+    return true;
+  }
+
   bool try_lock_shared() {
     auto lock = std::unique_lock{mtx};
     if (state != UNIQUE) {
@@ -71,6 +80,22 @@ struct ResourceLock {
     }
   }
 
+  void upgrade_to_unique() {
+    auto lock = std::unique_lock{mtx};
+    cv.wait(lock, [this] { return count == 1; });
+    state = UNIQUE;
+    count = 0;
+  }
+
+  template <class Rep, class Period>
+  bool try_upgrade_to_unique(const std::chrono::duration<Rep, Period> &timeout_duration) {
+    auto lock = std::unique_lock{mtx};
+    if (!cv.wait_for(lock, timeout_duration, [this] { return count == 1; })) return false;
+    state = UNIQUE;
+    count = 0;
+    return true;
+  }
+
  private:
   std::mutex mtx;
   std::condition_variable cv;
@@ -78,4 +103,46 @@ struct ResourceLock {
   uint64_t count = 0;
 };
 
+struct ResourceLockGuard {
+ private:
+  enum states { UNIQUE, SHARED };
+
+ public:
+  explicit ResourceLockGuard(ResourceLock &thing)
+      : ptr{&thing}, state{[this]() {
+          ptr->lock_shared();
+          return SHARED;
+        }()} {}
+
+  void upgrade_to_unique() {
+    if (state == SHARED) {
+      ptr->upgrade_to_unique();
+      state = UNIQUE;
+    }
+  }
+
+  template <class Rep, class Period>
+  bool try_upgrade_to_unique(const std::chrono::duration<Rep, Period> &timeout_duration) {
+    if (state != SHARED) return true;                                 // already locked
+    if (!ptr->try_upgrade_to_unique(timeout_duration)) return false;  // timeout
+    state = UNIQUE;
+    return true;
+  }
+
+  ~ResourceLockGuard() {
+    switch (state) {
+      case UNIQUE:
+        ptr->unlock();
+        break;
+      case SHARED:
+        ptr->unlock_shared();
+        break;
+    }
+  }
+
+ private:
+  ResourceLock *ptr;
+  states state;
+};
+
 }  // namespace memgraph::utils
diff --git a/src/utils/scheduler.hpp b/src/utils/scheduler.hpp
index d96178598..742271a95 100644
--- a/src/utils/scheduler.hpp
+++ b/src/utils/scheduler.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -58,28 +58,40 @@ class Scheduler {
         // the start of the program. Since Server will log some messages on
         // the program start we let him log first and we make sure by first
         // waiting that funcion f will not log before it.
+        // Check for pause also.
         std::unique_lock<std::mutex> lk(mutex_);
         auto now = std::chrono::system_clock::now();
         start_time += pause;
         if (start_time > now) {
-          condition_variable_.wait_until(lk, start_time, [&] { return is_working_.load() == false; });
+          condition_variable_.wait_until(lk, start_time, [&] { return !is_working_.load(); });
         } else {
           start_time = now;
         }
 
+        pause_cv_.wait(lk, [&] { return !is_paused_.load(); });
+
         if (!is_working_) break;
         f();
       }
     });
   }
 
+  void Resume() {
+    is_paused_.store(false);
+    pause_cv_.notify_one();
+  }
+
+  void Pause() { is_paused_.store(true); }
+
   /**
    * @brief Stops the thread execution. This is a blocking call and may take as
    * much time as one call to the function given previously to Run takes.
    * @throw std::system_error
    */
   void Stop() {
+    is_paused_.store(false);
     is_working_.store(false);
+    pause_cv_.notify_one();
     condition_variable_.notify_one();
     if (thread_.joinable()) thread_.join();
   }
@@ -97,6 +109,16 @@ class Scheduler {
    */
   std::atomic<bool> is_working_{false};
 
+  /**
+   * Variable is true when thread is paused.
+   */
+  std::atomic<bool> is_paused_{false};
+
+  /*
+   * Wait until the thread is resumed.
+   */
+  std::condition_variable pause_cv_;
+
   /**
    * Mutex used to synchronize threads using condition variable.
    */
diff --git a/src/utils/settings.cpp b/src/utils/settings.cpp
index 330b43f48..4768edc42 100644
--- a/src/utils/settings.cpp
+++ b/src/utils/settings.cpp
@@ -27,6 +27,7 @@ void Settings::Finalize() {
   std::lock_guard settings_guard{settings_lock_};
   storage_.reset();
   on_change_callbacks_.clear();
+  validations_.clear();
 }
 
 void Settings::RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback,
diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp
index de4892375..193d83b0b 100644
--- a/src/utils/skip_list.hpp
+++ b/src/utils/skip_list.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -365,9 +365,7 @@ class SkipListGc final {
         leftover.Push(*item);
       }
     }
-    while ((item = leftover.Pop())) {
-      deleted_.Push(*item);
-    }
+    deleted_ = std::move(leftover);
   }
 
   MemoryResource *GetMemoryResource() const { return memory_; }
@@ -384,11 +382,14 @@ class SkipListGc final {
     }
 
     // Delete all items that have to be garbage collected.
-    std::optional<TDeleted> item;
-    while ((item = deleted_.Pop())) {
-      size_t bytes = SkipListNodeSize(*item->second);
-      item->second->~TNode();
-      memory_->Deallocate(item->second, bytes);
+    {
+      std::optional<TDeleted> item;
+      std::unique_lock guard(lock_);
+      while ((item = deleted_.Pop())) {
+        size_t bytes = SkipListNodeSize(*item->second);
+        item->second->~TNode();
+        memory_->Deallocate(item->second, bytes);
+      }
     }
 
     // Reset all variables.
@@ -591,13 +592,20 @@ class SkipList final : detail::SkipListNode_base {
     Iterator(TNode *node) : node_(node) {}
 
    public:
-    TObj &operator*() const { return node_->obj; }
+    using value_type = TObj;
+    using difference_type = std::ptrdiff_t;
 
-    TObj *operator->() const { return &node_->obj; }
+    Iterator() = default;
+    Iterator(Iterator const &) = default;
+    Iterator(Iterator &&) = default;
+    Iterator &operator=(Iterator const &) = default;
+    Iterator &operator=(Iterator &&) = default;
 
-    bool operator==(const Iterator &other) const { return node_ == other.node_; }
+    value_type &operator*() const { return node_->obj; }
 
-    bool operator!=(const Iterator &other) const { return node_ != other.node_; }
+    value_type *operator->() const { return &node_->obj; }
+
+    friend bool operator==(Iterator const &lhs, Iterator const &rhs) { return lhs.node_ == rhs.node_; }
 
     Iterator &operator++() {
       while (true) {
@@ -610,8 +618,14 @@ class SkipList final : detail::SkipListNode_base {
       }
     }
 
+    Iterator operator++(int) {
+      Iterator old = *this;
+      ++(*this);
+      return old;
+    }
+
    private:
-    TNode *node_;
+    TNode *node_{nullptr};
   };
 
   class ConstIterator final {
@@ -621,15 +635,22 @@ class SkipList final : detail::SkipListNode_base {
     ConstIterator(TNode *node) : node_(node) {}
 
    public:
+    using value_type = TObj const;
+    using difference_type = std::ptrdiff_t;
+
+    ConstIterator() = default;
+    ConstIterator(ConstIterator const &) = default;
+    ConstIterator(ConstIterator &&) = default;
+    ConstIterator &operator=(ConstIterator const &) = default;
+    ConstIterator &operator=(ConstIterator &&) = default;
+
     ConstIterator(const Iterator &it) : node_(it.node_) {}
 
-    const TObj &operator*() const { return node_->obj; }
+    value_type &operator*() const { return node_->obj; }
 
-    const TObj *operator->() const { return &node_->obj; }
+    value_type *operator->() const { return &node_->obj; }
 
-    bool operator==(const ConstIterator &other) const { return node_ == other.node_; }
-
-    bool operator!=(const ConstIterator &other) const { return node_ != other.node_; }
+    friend bool operator==(ConstIterator const &lhs, ConstIterator const &rhs) { return lhs.node_ == rhs.node_; }
 
     ConstIterator &operator++() {
       while (true) {
@@ -642,6 +663,12 @@ class SkipList final : detail::SkipListNode_base {
       }
     }
 
+    ConstIterator operator++(int) {
+      ConstIterator old = *this;
+      ++(*this);
+      return old;
+    }
+
    private:
     TNode *node_;
   };
@@ -653,6 +680,10 @@ class SkipList final : detail::SkipListNode_base {
     explicit Accessor(SkipList *skiplist) : skiplist_(skiplist), id_(skiplist->gc_.AllocateId()) {}
 
    public:
+    using value_type = TObj;
+    using iterator = Iterator;
+    using const_iterator = ConstIterator;
+
     ~Accessor() {
       if (skiplist_ != nullptr) skiplist_->gc_.ReleaseId(id_);
     }
@@ -695,7 +726,7 @@ class SkipList final : detail::SkipListNode_base {
     /// @return bool indicating whether the item exists
     template <typename TKey>
     bool contains(const TKey &key) const {
-      return skiplist_->template contains(key);
+      return skiplist_->contains(key);
     }
 
     /// Finds the key in the list and returns an iterator to the item.
@@ -703,8 +734,17 @@ class SkipList final : detail::SkipListNode_base {
     /// @return Iterator to the item in the list, will be equal to `end()` when
     ///                  the key isn't found
     template <typename TKey>
-    Iterator find(const TKey &key) const {
-      return skiplist_->template find(key);
+    Iterator find(const TKey &key) {
+      return skiplist_->find(key);
+    }
+
+    /// Finds the key in the list and returns an iterator to the item.
+    ///
+    /// @return ConstIterator to the item in the list, will be equal to `cend()` when
+    ///                  the key isn't found
+    template <typename TKey>
+    ConstIterator find(const TKey &key) const {
+      return skiplist_->find(key);
     }
 
     /// Finds the key or the first larger key in the list and returns an
@@ -713,8 +753,18 @@ class SkipList final : detail::SkipListNode_base {
     /// @return Iterator to the item in the list, will be equal to `end()` when
     ///                  no items match the search
     template <typename TKey>
-    Iterator find_equal_or_greater(const TKey &key) const {
-      return skiplist_->template find_equal_or_greater(key);
+    Iterator find_equal_or_greater(const TKey &key) {
+      return skiplist_->find_equal_or_greater(key);
+    }
+
+    /// Finds the key or the first larger key in the list and returns an
+    /// iterator to the item.
+    ///
+    /// @return ConstIterator to the item in the list, will be equal to `end()` when
+    ///                  no items match the search
+    template <typename TKey>
+    ConstIterator find_equal_or_greater(const TKey &key) const {
+      return skiplist_->find_equal_or_greater(key);
     }
 
     /// Estimates the number of items that are contained in the list that are
@@ -727,7 +777,7 @@ class SkipList final : detail::SkipListNode_base {
     /// @return uint64_t estimated count of identical items in the list
     template <typename TKey>
     uint64_t estimate_count(const TKey &key, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
-      return skiplist_->template estimate_count(key, max_layer_for_estimation);
+      return skiplist_->estimate_count(key, max_layer_for_estimation);
     }
 
     /// Estimates the number of items that are contained in the list that are
@@ -742,7 +792,7 @@ class SkipList final : detail::SkipListNode_base {
     uint64_t estimate_range_count(const std::optional<utils::Bound<TKey>> &lower,
                                   const std::optional<utils::Bound<TKey>> &upper,
                                   int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
-      return skiplist_->template estimate_range_count(lower, upper, max_layer_for_estimation);
+      return skiplist_->estimate_range_count(lower, upper, max_layer_for_estimation);
     }
 
     /// Estimates the average number of objects in the list that have the same
@@ -759,7 +809,7 @@ class SkipList final : detail::SkipListNode_base {
     template <typename TCallable>
     uint64_t estimate_average_number_of_equals(
         const TCallable &equal_cmp, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
-      return skiplist_->template estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
+      return skiplist_->estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
     }
 
     /// Removes the key from the list.
@@ -767,7 +817,7 @@ class SkipList final : detail::SkipListNode_base {
     /// @return bool indicating whether the removal was successful
     template <typename TKey>
     bool remove(const TKey &key) {
-      return skiplist_->template remove(key);
+      return skiplist_->remove(key);
     }
 
     /// Returns the number of items contained in the list.
@@ -787,6 +837,10 @@ class SkipList final : detail::SkipListNode_base {
     explicit ConstAccessor(const SkipList *skiplist) : skiplist_(skiplist), id_(skiplist->gc_.AllocateId()) {}
 
    public:
+    using value_type = TObj;
+    using iterator = ConstIterator;
+    using const_iterator = ConstIterator;
+
     ~ConstAccessor() {
       if (skiplist_ != nullptr) skiplist_->gc_.ReleaseId(id_);
     }
@@ -812,35 +866,35 @@ class SkipList final : detail::SkipListNode_base {
 
     template <typename TKey>
     bool contains(const TKey &key) const {
-      return skiplist_->template contains(key);
+      return skiplist_->contains(key);
     }
 
     template <typename TKey>
     ConstIterator find(const TKey &key) const {
-      return skiplist_->template find(key);
+      return skiplist_->find(key);
     }
 
     template <typename TKey>
     ConstIterator find_equal_or_greater(const TKey &key) const {
-      return skiplist_->template find_equal_or_greater(key);
+      return skiplist_->find_equal_or_greater(key);
     }
 
     template <typename TKey>
     uint64_t estimate_count(const TKey &key, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
-      return skiplist_->template estimate_count(key, max_layer_for_estimation);
+      return skiplist_->estimate_count(key, max_layer_for_estimation);
     }
 
     template <typename TKey>
     uint64_t estimate_range_count(const std::optional<utils::Bound<TKey>> &lower,
                                   const std::optional<utils::Bound<TKey>> &upper,
                                   int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
-      return skiplist_->template estimate_range_count(lower, upper, max_layer_for_estimation);
+      return skiplist_->estimate_range_count(lower, upper, max_layer_for_estimation);
     }
 
     template <typename TCallable>
     uint64_t estimate_average_number_of_equals(
         const TCallable &equal_cmp, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const {
-      return skiplist_->template estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
+      return skiplist_->estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation);
     }
 
     uint64_t size() const { return skiplist_->size(); }
@@ -975,40 +1029,46 @@ class SkipList final : detail::SkipListNode_base {
         continue;
       }
 
-      std::unique_lock<SpinLock> guards[kSkipListMaxHeight];
-      TNode *pred, *succ, *prev_pred = nullptr;
-      bool valid = true;
-      // The paper has a wrong condition here. In the paper it states that this
-      // loop should have `(layer <= top_layer)`, but that isn't correct.
-      for (int layer = 0; valid && (layer < top_layer); ++layer) {
-        pred = preds[layer];
-        succ = succs[layer];
-        if (pred != prev_pred) {
-          guards[layer] = std::unique_lock<SpinLock>(pred->lock);
-          prev_pred = pred;
+      TNode *new_node;
+      {
+        TNode *prev_pred = nullptr;
+        bool valid = true;
+        std::unique_lock<SpinLock> guards[kSkipListMaxHeight];
+        // The paper has a wrong condition here. In the paper it states that this
+        // loop should have `(layer <= top_layer)`, but that isn't correct.
+        for (int layer = 0; valid && (layer < top_layer); ++layer) {
+          TNode *pred = preds[layer];
+          TNode *succ = succs[layer];
+          if (pred != prev_pred) {
+            guards[layer] = std::unique_lock<SpinLock>(pred->lock);
+            prev_pred = pred;
+          }
+          // Existence test is missing in the paper.
+          valid = !pred->marked.load(std::memory_order_acquire) &&
+                  pred->nexts[layer].load(std::memory_order_acquire) == succ &&
+                  (succ == nullptr || !succ->marked.load(std::memory_order_acquire));
         }
-        // Existence test is missing in the paper.
-        valid = !pred->marked.load(std::memory_order_acquire) &&
-                pred->nexts[layer].load(std::memory_order_acquire) == succ &&
-                (succ == nullptr || !succ->marked.load(std::memory_order_acquire));
-      }
 
-      if (!valid) continue;
+        if (!valid) continue;
 
-      size_t node_bytes = sizeof(TNode) + top_layer * sizeof(std::atomic<TNode *>);
-      void *ptr = GetMemoryResource()->Allocate(node_bytes);
-      // `calloc` would be faster, but the API has no such call.
-      memset(ptr, 0, node_bytes);
-      auto *new_node = static_cast<TNode *>(ptr);
-      // Construct through allocator so it propagates if needed.
-      Allocator<TNode> allocator(GetMemoryResource());
-      allocator.construct(new_node, top_layer, std::forward<TObjUniv>(object));
+        size_t node_bytes = sizeof(TNode) + top_layer * sizeof(std::atomic<TNode *>);
 
-      // The paper is also wrong here. It states that the loop should go up to
-      // `top_layer` which is wrong.
-      for (int layer = 0; layer < top_layer; ++layer) {
-        new_node->nexts[layer].store(succs[layer], std::memory_order_release);
-        preds[layer]->nexts[layer].store(new_node, std::memory_order_release);
+        MemoryResource *memoryResource = GetMemoryResource();
+        void *ptr = memoryResource->Allocate(node_bytes);
+        // `calloc` would be faster, but the API has no such call.
+        memset(ptr, 0, node_bytes);
+        new_node = static_cast<TNode *>(ptr);
+
+        // Construct through allocator so it propagates if needed.
+        Allocator<TNode> allocator(memoryResource);
+        allocator.construct(new_node, top_layer, std::forward<TObjUniv>(object));
+
+        // The paper is also wrong here. It states that the loop should go up to
+        // `top_layer` which is wrong.
+        for (int layer = 0; layer < top_layer; ++layer) {
+          new_node->nexts[layer].store(succs[layer], std::memory_order_release);
+          preds[layer]->nexts[layer].store(new_node, std::memory_order_release);
+        }
       }
 
       new_node->fully_linked.store(true, std::memory_order_release);
@@ -1018,26 +1078,33 @@ class SkipList final : detail::SkipListNode_base {
   }
 
   template <typename TKey>
-  bool contains(const TKey &key) const {
-    TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight];
-    int layer_found = find_node(key, preds, succs);
-    return (layer_found != -1 && succs[layer_found]->fully_linked.load(std::memory_order_acquire) &&
-            !succs[layer_found]->marked.load(std::memory_order_acquire));
-  }
-
-  template <typename TKey>
-  Iterator find(const TKey &key) const {
+  SkipListNode<TObj> *find_(const TKey &key) const {
     TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight];
     int layer_found = find_node(key, preds, succs);
     if (layer_found != -1 && succs[layer_found]->fully_linked.load(std::memory_order_acquire) &&
         !succs[layer_found]->marked.load(std::memory_order_acquire)) {
-      return Iterator{succs[layer_found]};
+      return succs[layer_found];
     }
-    return Iterator{nullptr};
+    return nullptr;
   }
 
   template <typename TKey>
-  Iterator find_equal_or_greater(const TKey &key) const {
+  bool contains(const TKey &key) const {
+    return find_(key) != nullptr;
+  }
+
+  template <typename TKey>
+  Iterator find(const TKey &key) {
+    return {find_(key)};
+  }
+
+  template <typename TKey>
+  ConstIterator find(const TKey &key) const {
+    return {find_(key)};
+  }
+
+  template <typename TKey>
+  Iterator find_equal_or_greater_(const TKey &key) const {
     TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight];
     find_node(key, preds, succs);
     if (succs[0] && succs[0]->fully_linked.load(std::memory_order_acquire) &&
@@ -1047,6 +1114,16 @@ class SkipList final : detail::SkipListNode_base {
     return Iterator{nullptr};
   }
 
+  template <typename TKey>
+  Iterator find_equal_or_greater(const TKey &key) {
+    return {find_equal_or_greater_(key)};
+  }
+
+  template <typename TKey>
+  ConstIterator find_equal_or_greater(const TKey &key) const {
+    return {find_equal_or_greater_(key)};
+  }
+
   template <typename TKey>
   uint64_t estimate_count(const TKey &key, int max_layer_for_estimation) const {
     MG_ASSERT(max_layer_for_estimation >= 1 && max_layer_for_estimation <= kSkipListMaxHeight,
diff --git a/src/utils/stat.hpp b/src/utils/stat.hpp
index 7e4fab29b..4c2eec6d6 100644
--- a/src/utils/stat.hpp
+++ b/src/utils/stat.hpp
@@ -25,17 +25,32 @@ namespace memgraph::utils {
 static constexpr int64_t VM_MAX_MAP_COUNT_DEFAULT{-1};
 
 /// Returns the number of bytes a directory is using on disk. If the given path
-/// isn't a directory, zero will be returned.
+/// isn't a directory, zero will be returned. If there are some files with
+/// wrong permission, it will be skipped
 template <bool IgnoreSymlink = true>
 inline uint64_t GetDirDiskUsage(const std::filesystem::path &path) {
   if (!std::filesystem::is_directory(path)) return 0;
 
+  if (!utils::HasReadAccess(path)) {
+    spdlog::warn(
+        "Skipping directory path on collecting directory disk usage '{}' because it is not readable, check file "
+        "ownership and read permissions!",
+        path);
+    return 0;
+  }
   uint64_t size = 0;
-  for (auto &p : std::filesystem::directory_iterator(path)) {
+  for (const auto &p : std::filesystem::directory_iterator(path)) {
     if (IgnoreSymlink && std::filesystem::is_symlink(p)) continue;
     if (std::filesystem::is_directory(p)) {
       size += GetDirDiskUsage(p);
     } else if (std::filesystem::is_regular_file(p)) {
+      if (!utils::HasReadAccess(p)) {
+        spdlog::warn(
+            "Skipping file path on collecting directory disk usage '{}' because it is not readable, check file "
+            "ownership and read permissions!",
+            p);
+        continue;
+      }
       size += std::filesystem::file_size(p);
     }
   }
diff --git a/src/utils/string.hpp b/src/utils/string.hpp
index 833c158c8..8593fc57f 100644
--- a/src/utils/string.hpp
+++ b/src/utils/string.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 #include <charconv>
 #include <cstdint>
 #include <cstring>
+#include <iomanip>
 #include <iostream>
 #include <iterator>
 #include <random>
@@ -459,4 +460,30 @@ inline std::string_view Substr(const std::string_view string, size_t pos = 0, si
   return string.substr(pos, len);
 }
 
+/**
+ * Convert a double value to a string representation.
+ * Precision of converted value is 16.
+ * Function also removes trailing zeros after the dot.
+ *
+ * @param value The double value to be converted.
+ *
+ * @return The string representation of the double value.
+ *
+ * @throws None
+ */
+inline std::string DoubleToString(const double value) {
+  static const int PRECISION = 15;
+
+  std::stringstream ss;
+  ss << std::setprecision(PRECISION) << std::fixed << value;
+  auto sv = ss.view();
+
+  // Because of setprecision and fixed manipulator we are guaranteed to have the dot
+  sv = sv.substr(0, sv.find_last_not_of('0') + 1);
+  if (sv.ends_with('.')) {
+    sv = sv.substr(0, sv.size() - 1);
+  }
+  return std::string(sv);
+}
+
 }  // namespace memgraph::utils
diff --git a/src/utils/thread_pool.hpp b/src/utils/thread_pool.hpp
index 8597a78a1..c6f0bb6ee 100644
--- a/src/utils/thread_pool.hpp
+++ b/src/utils/thread_pool.hpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,6 +23,16 @@
 
 namespace memgraph::utils {
 
+template <typename Func>
+struct CopyMovableFunctionWrapper {
+  CopyMovableFunctionWrapper(Func &&func) : func_{std::make_shared<Func>(std::move(func))} {}
+
+  void operator()() { (*func_)(); }
+
+ private:
+  std::shared_ptr<Func> func_;
+};
+
 class ThreadPool {
   using TaskSignature = std::function<void()>;
 
diff --git a/src/utils/typeinfo.hpp b/src/utils/typeinfo.hpp
index 682b5ac55..fd0d1fdeb 100644
--- a/src/utils/typeinfo.hpp
+++ b/src/utils/typeinfo.hpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,9 +17,10 @@
 namespace memgraph::utils {
 
 enum class TypeId : uint64_t {
+  UNKNOWN = 0,
+
   // Operators
-  UNKNOWN,
-  LOGICAL_OPERATOR,
+  LOGICAL_OPERATOR = 1000,
   ONCE,
   NODE_CREATION_INFO,
   CREATE_NODE,
@@ -69,7 +70,8 @@ enum class TypeId : uint64_t {
   HASH_JOIN,
 
   // Replication
-  REP_APPEND_DELTAS_REQ,
+  // NOTE: these NEED to be stable in the 2000+ range (see rpc version)
+  REP_APPEND_DELTAS_REQ = 2000,
   REP_APPEND_DELTAS_RES,
   REP_HEARTBEAT_REQ,
   REP_HEARTBEAT_RES,
@@ -83,9 +85,23 @@ enum class TypeId : uint64_t {
   REP_CURRENT_WAL_RES,
   REP_TIMESTAMP_REQ,
   REP_TIMESTAMP_RES,
+  REP_CREATE_DATABASE_REQ,
+  REP_CREATE_DATABASE_RES,
+  REP_DROP_DATABASE_REQ,
+  REP_DROP_DATABASE_RES,
+  REP_SYSTEM_HEARTBEAT_REQ,
+  REP_SYSTEM_HEARTBEAT_RES,
+  REP_SYSTEM_RECOVERY_REQ,
+  REP_SYSTEM_RECOVERY_RES,
+
+  // Coordinator
+  COORD_FAILOVER_REQ,
+  COORD_FAILOVER_RES,
+  COORD_SET_REPL_MAIN_REQ,
+  COORD_SET_REPL_MAIN_RES,
 
   // AST
-  AST_LABELIX,
+  AST_LABELIX = 3000,
   AST_PROPERTYIX,
   AST_EDGETYPEIX,
   AST_TREE,
@@ -190,8 +206,11 @@ enum class TypeId : uint64_t {
   AST_MULTI_DATABASE_QUERY,
   AST_SHOW_DATABASES,
   AST_EDGE_IMPORT_MODE_QUERY,
+  AST_PATTERN_COMPREHENSION,
+  AST_COORDINATOR_QUERY,
+
   // Symbol
-  SYMBOL,
+  SYMBOL = 4000,
 };
 
 /// Type information on a C++ type.
diff --git a/src/utils/uuid.cpp b/src/utils/uuid.cpp
index 9b13b8965..fbcf662de 100644
--- a/src/utils/uuid.cpp
+++ b/src/utils/uuid.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2023 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -10,8 +10,8 @@
 // licenses/APL.txt.
 
 #include "utils/uuid.hpp"
-
 #include <uuid/uuid.h>
+#include "slk/serialization.hpp"
 
 namespace memgraph::utils {
 
@@ -24,3 +24,13 @@ std::string GenerateUUID() {
 }
 
 }  // namespace memgraph::utils
+
+// Serialize UUID
+namespace memgraph::slk {
+void Save(const memgraph::utils::UUID &self, memgraph::slk::Builder *builder) {
+  const auto &arr = static_cast<utils::UUID::arr_t>(self);
+  memgraph::slk::Save(arr, builder);
+}
+
+void Load(memgraph::utils::UUID *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(&self->uuid, reader); }
+}  // namespace memgraph::slk
diff --git a/src/utils/uuid.hpp b/src/utils/uuid.hpp
index 8bbb1a1a1..bca55d73b 100644
--- a/src/utils/uuid.hpp
+++ b/src/utils/uuid.hpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2023 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,8 +11,22 @@
 
 #pragma once
 
+#include <uuid/uuid.h>
+#include <array>
+#include <json/json.hpp>
 #include <string>
 
+namespace memgraph::utils {
+struct UUID;
+}
+
+namespace memgraph::slk {
+class Reader;
+class Builder;
+void Save(const ::memgraph::utils::UUID &self, Builder *builder);
+void Load(::memgraph::utils::UUID *self, Reader *reader);
+}  // namespace memgraph::slk
+
 namespace memgraph::utils {
 
 /**
@@ -20,4 +34,35 @@ namespace memgraph::utils {
  */
 std::string GenerateUUID();
 
+struct UUID {
+  using arr_t = std::array<unsigned char, 16>;
+
+  UUID() { uuid_generate(uuid.data()); }
+  explicit operator std::string() const {
+    auto decoded = std::array<char, UUID_STR_LEN>{};
+    uuid_unparse(uuid.data(), decoded.data());
+    return std::string{decoded.data(), UUID_STR_LEN - 1};
+  }
+
+  explicit operator arr_t() const { return uuid; }
+
+  friend bool operator==(UUID const &, UUID const &) = default;
+
+ private:
+  friend void to_json(nlohmann::json &j, const UUID &uuid);
+  friend void from_json(const nlohmann::json &j, UUID &uuid);
+  friend void ::memgraph::slk::Load(UUID *self, slk::Reader *reader);
+  explicit UUID(arr_t const &arr) : uuid(arr) {}
+
+  arr_t uuid;
+};
+
+inline void to_json(nlohmann::json &j, const UUID &uuid) { j = nlohmann::json(uuid.uuid); }
+
+inline void from_json(const nlohmann::json &j, UUID &uuid) {
+  auto arr = UUID::arr_t{};
+  j.get_to(arr);
+  uuid = UUID(arr);
+}
+
 }  // namespace memgraph::utils
diff --git a/tests/benchmark/query/eval.cpp b/tests/benchmark/query/eval.cpp
index 5e5acea7f..92ba67cd6 100644
--- a/tests/benchmark/query/eval.cpp
+++ b/tests/benchmark/query/eval.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 // The following classes are wrappers for memgraph::utils::MemoryResource, so that we can
 // use BENCHMARK_TEMPLATE
 
@@ -40,7 +41,7 @@ static void MapLiteral(benchmark::State &state) {
   TMemory memory;
   memgraph::query::Frame frame(symbol_table.max_position(), memory.get());
   std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage());
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *> elements;
   for (int64_t i = 0; i < state.range(0); ++i) {
@@ -71,7 +72,7 @@ static void AdditionOperator(benchmark::State &state) {
   TMemory memory;
   memgraph::query::Frame frame(symbol_table.max_position(), memory.get());
   std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage());
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   memgraph::query::Expression *expr = ast.Create<memgraph::query::PrimitiveLiteral>(0);
   for (int64_t i = 0; i < state.range(0); ++i) {
diff --git a/tests/benchmark/query/execution.cpp b/tests/benchmark/query/execution.cpp
index 771f0ac1f..d49b14fc3 100644
--- a/tests/benchmark/query/execution.cpp
+++ b/tests/benchmark/query/execution.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,6 +23,7 @@
 // variable of the same name, EOF.
 // This hides the definition of the macro which causes
 // the compilation to fail.
+#include "query/parameters.hpp"
 #include "query/plan/planner.hpp"
 //////////////////////////////////////////////////////
 #include "communication/result_stream_faker.hpp"
@@ -32,6 +33,8 @@
 #include "query/interpreter.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 // The following classes are wrappers for memgraph::utils::MemoryResource, so that we can
 // use BENCHMARK_TEMPLATE
 
@@ -61,7 +64,7 @@ class PoolResource final {
 };
 
 static void AddVertices(memgraph::storage::Storage *db, int vertex_count) {
-  auto dba = db->Access();
+  auto dba = db->Access(ReplicationRole::MAIN);
   for (int i = 0; i < vertex_count; i++) dba->CreateVertex();
   MG_ASSERT(!dba->Commit().HasError());
 }
@@ -70,7 +73,7 @@ static const char *kStartLabel = "start";
 
 static void AddStarGraph(memgraph::storage::Storage *db, int spoke_count, int depth) {
   {
-    auto dba = db->Access();
+    auto dba = db->Access(ReplicationRole::MAIN);
     auto center_vertex = dba->CreateVertex();
     MG_ASSERT(center_vertex.AddLabel(dba->NameToLabel(kStartLabel)).HasValue());
     for (int i = 0; i < spoke_count; ++i) {
@@ -84,14 +87,14 @@ static void AddStarGraph(memgraph::storage::Storage *db, int spoke_count, int de
     MG_ASSERT(!dba->Commit().HasError());
   }
   {
-    auto unique_acc = db->UniqueAccess();
+    auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN);
     MG_ASSERT(!unique_acc->CreateIndex(db->NameToLabel(kStartLabel)).HasError());
   }
 }
 
 static void AddTree(memgraph::storage::Storage *db, int vertex_count) {
   {
-    auto dba = db->Access();
+    auto dba = db->Access(ReplicationRole::MAIN);
     std::vector<memgraph::storage::VertexAccessor> vertices;
     vertices.reserve(vertex_count);
     auto root = dba->CreateVertex();
@@ -109,7 +112,7 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) {
     MG_ASSERT(!dba->Commit().HasError());
   }
   {
-    auto unique_acc = db->UniqueAccess();
+    auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN);
     MG_ASSERT(!unique_acc->CreateIndex(db->NameToLabel(kStartLabel)).HasError());
   }
 }
@@ -117,10 +120,11 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) {
 static memgraph::query::CypherQuery *ParseCypherQuery(const std::string &query_string,
                                                       memgraph::query::AstStorage *ast) {
   memgraph::query::frontend::ParsingContext parsing_context;
+  memgraph::query::Parameters parameters;
   parsing_context.is_query_cached = false;
   memgraph::query::frontend::opencypher::Parser parser(query_string);
   // Convert antlr4 AST into Memgraph AST.
-  memgraph::query::frontend::CypherMainVisitor cypher_visitor(parsing_context, ast);
+  memgraph::query::frontend::CypherMainVisitor cypher_visitor(parsing_context, ast, &parameters);
   cypher_visitor.visit(parser.tree());
   return memgraph::utils::Downcast<memgraph::query::CypherQuery>(cypher_visitor.query());
 };
@@ -132,7 +136,7 @@ static void Distinct(benchmark::State &state) {
   memgraph::query::Parameters parameters;
   std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage());
   AddVertices(db.get(), state.range(0));
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto query_string = "MATCH (s) RETURN DISTINCT s";
   auto *cypher_query = ParseCypherQuery(query_string, &ast);
@@ -185,7 +189,7 @@ static void ExpandVariable(benchmark::State &state) {
   AddStarGraph(db.get(), state.range(0), state.range(1));
   memgraph::query::SymbolTable symbol_table;
   auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::DEPTH_FIRST, &symbol_table);
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -225,7 +229,7 @@ static void ExpandBfs(benchmark::State &state) {
   AddTree(db.get(), state.range(0));
   memgraph::query::SymbolTable symbol_table;
   auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::BREADTH_FIRST, &symbol_table);
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -261,7 +265,7 @@ static void ExpandShortest(benchmark::State &state) {
   auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::BREADTH_FIRST, &symbol_table);
   expand_variable.common_.existing_node = true;
   auto dest_symbol = expand_variable.common_.node_symbol;
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -303,7 +307,7 @@ static void ExpandWeightedShortest(benchmark::State &state) {
       symbol_table.CreateSymbol("edge", false), symbol_table.CreateSymbol("vertex", false),
       ast.Create<memgraph::query::PrimitiveLiteral>(1)};
   auto dest_symbol = expand_variable.common_.node_symbol;
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -349,7 +353,7 @@ static void Accumulate(benchmark::State &state) {
   }
   memgraph::query::plan::Accumulate accumulate(scan_all, symbols,
                                                /* advance_command= */ false);
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -399,7 +403,7 @@ static void Aggregate(benchmark::State &state) {
                             symbol_table.CreateSymbol("out" + std::to_string(i), false)});
   }
   memgraph::query::plan::Aggregate aggregate(scan_all, aggregations, group_by, symbols);
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -450,7 +454,7 @@ static void OrderBy(benchmark::State &state) {
     sort_items.push_back({memgraph::query::Ordering::ASC, ast.Create<memgraph::query::PrimitiveLiteral>(rand_value)});
   }
   memgraph::query::plan::OrderBy order_by(scan_all, sort_items, symbols);
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -489,7 +493,7 @@ static void Unwind(benchmark::State &state) {
   auto *list_expr = ast.Create<memgraph::query::Identifier>("list")->MapTo(list_sym);
   auto out_sym = symbol_table.CreateSymbol("out", false);
   memgraph::query::plan::Unwind unwind(scan_all, list_expr, out_sym);
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // We need to only set the memory for temporary (per pull) evaluations
   TMemory per_pull_memory;
@@ -527,7 +531,7 @@ static void Foreach(benchmark::State &state) {
       std::make_shared<memgraph::query::plan::CreateNode>(nullptr, memgraph::query::plan::NodeCreationInfo{});
   auto foreach = std::make_shared<memgraph::query::plan::Foreach>(nullptr, std::move(create_node), list_expr, out_sym);
 
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   TMemory per_pull_memory;
   memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()};
diff --git a/tests/benchmark/query/planner.cpp b/tests/benchmark/query/planner.cpp
index 8cc90fe05..c70de0869 100644
--- a/tests/benchmark/query/planner.cpp
+++ b/tests/benchmark/query/planner.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,9 +16,12 @@
 #include "query/frontend/semantic/symbol_generator.hpp"
 #include "query/plan/cost_estimator.hpp"
 #include "query/plan/planner.hpp"
+#include "query/plan/rewrite/index_lookup.hpp"
 #include "query/plan/vertex_count_cache.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 // Add chained MATCH (node1) -- (node2), MATCH (node2) -- (node3) ... clauses.
 static memgraph::query::CypherQuery *AddChainedMatches(int num_matches, memgraph::query::AstStorage &storage) {
   auto *query = storage.Create<memgraph::query::CypherQuery>();
@@ -44,7 +47,7 @@ static memgraph::query::CypherQuery *AddChainedMatches(int num_matches, memgraph
 
 static void BM_PlanChainedMatches(benchmark::State &state) {
   std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage());
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   while (state.KeepRunning()) {
     state.PauseTiming();
@@ -95,10 +98,10 @@ static auto CreateIndexedVertices(int index_count, int vertex_count, memgraph::s
   auto label = db->NameToLabel("label");
   auto prop = db->NameToProperty("prop");
   {
-    auto unique_acc = db->UniqueAccess();
+    auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
   }
-  auto dba = db->Access();
+  auto dba = db->Access(ReplicationRole::MAIN);
   for (int vi = 0; vi < vertex_count; ++vi) {
     for (int index = 0; index < index_count; ++index) {
       auto vertex = dba->CreateVertex();
@@ -117,7 +120,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
   int index_count = state.range(0);
   int vertex_count = state.range(1);
   std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, db.get());
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   memgraph::query::Parameters parameters;
   while (state.KeepRunning()) {
@@ -134,7 +137,8 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
     auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>(
         query_parts, &ctx);
     for (auto plan : plans) {
-      memgraph::query::plan::EstimatePlanCost(&dba, symbol_table, parameters, *plan);
+      memgraph::query::plan::EstimatePlanCost(&dba, symbol_table, parameters, *plan,
+                                              memgraph::query::plan::IndexHints());
     }
   }
 }
@@ -146,7 +150,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(benchmark::State &
   int index_count = state.range(0);
   int vertex_count = state.range(1);
   std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, db.get());
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto vertex_counts = memgraph::query::plan::MakeVertexCountCache(&dba);
   memgraph::query::Parameters parameters;
@@ -164,7 +168,8 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(benchmark::State &
     auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>(
         query_parts, &ctx);
     for (auto plan : plans) {
-      memgraph::query::plan::EstimatePlanCost(&vertex_counts, symbol_table, parameters, *plan);
+      memgraph::query::plan::EstimatePlanCost(&vertex_counts, symbol_table, parameters, *plan,
+                                              memgraph::query::plan::IndexHints());
     }
   }
 }
diff --git a/tests/benchmark/query/profile.cpp b/tests/benchmark/query/profile.cpp
index 1c28fd21c..49b97fc83 100644
--- a/tests/benchmark/query/profile.cpp
+++ b/tests/benchmark/query/profile.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -141,9 +141,9 @@ struct ScopedProfile {
     }
   }
 
-  Context *context;
-  ProfilingStats *stats;
-  unsigned long long start_time;
+  Context *context = nullptr;
+  ProfilingStats *stats = nullptr;
+  unsigned long long start_time{};
 };
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/tests/benchmark/storage_v2_gc.cpp b/tests/benchmark/storage_v2_gc.cpp
index 3941fcbe1..6f0e5712d 100644
--- a/tests/benchmark/storage_v2_gc.cpp
+++ b/tests/benchmark/storage_v2_gc.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,8 @@
 #include "storage/v2/storage.hpp"
 #include "utils/timer.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 // This benchmark should be run for a fixed amount of time that is
 // large compared to GC interval to make the output relevant.
 
@@ -42,7 +44,7 @@ void UpdateLabelFunc(int thread_id, memgraph::storage::Storage *storage,
 
   memgraph::utils::Timer timer;
   for (int iter = 0; iter < num_iterations; ++iter) {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     memgraph::storage::Gid gid = vertices.at(vertex_dist(gen));
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     MG_ASSERT(vertex.has_value(), "Vertex with GID {} doesn't exist", gid.AsUint());
@@ -61,7 +63,7 @@ int main(int argc, char *argv[]) {
     std::unique_ptr<memgraph::storage::Storage> storage(new memgraph::storage::InMemoryStorage(config.second));
     std::vector<memgraph::storage::Gid> vertices;
     {
-      auto acc = storage->Access();
+      auto acc = storage->Access(ReplicationRole::MAIN);
       for (int i = 0; i < FLAGS_num_vertices; ++i) {
         vertices.push_back(acc->CreateVertex().Gid());
       }
diff --git a/tests/benchmark/storage_v2_gc2.cpp b/tests/benchmark/storage_v2_gc2.cpp
index a8c0a589e..f3986edd3 100644
--- a/tests/benchmark/storage_v2_gc2.cpp
+++ b/tests/benchmark/storage_v2_gc2.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,8 @@
 #include "storage/v2/storage.hpp"
 #include "utils/timer.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 // This benchmark should be run for a fixed amount of time that is
 // large compared to GC interval to make the output relevant.
 
@@ -41,14 +43,14 @@ int main(int argc, char *argv[]) {
       std::array<memgraph::storage::Gid, 1> vertices;
       memgraph::storage::PropertyId pid;
       {
-        auto acc = storage->Access();
+        auto acc = storage->Access(ReplicationRole::MAIN);
         vertices[0] = acc->CreateVertex().Gid();
         pid = acc->NameToProperty("NEW_PROP");
         MG_ASSERT(!acc->Commit().HasError());
       }
 
       for (int iter = 0; iter != FLAGS_num_iterations; ++iter) {
-        auto acc = storage->Access();
+        auto acc = storage->Access(ReplicationRole::MAIN);
         auto vertex1 = acc->FindVertex(vertices[0], memgraph::storage::View::OLD);
         for (auto i = 0; i != FLAGS_num_poperties; ++i) {
           MG_ASSERT(!vertex1.value().SetProperty(pid, memgraph::storage::PropertyValue{i}).HasError());
diff --git a/tests/concurrent/storage_indices.cpp b/tests/concurrent/storage_indices.cpp
index a3c09d2e4..fc4d75a76 100644
--- a/tests/concurrent/storage_indices.cpp
+++ b/tests/concurrent/storage_indices.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -19,6 +19,8 @@
 #include "storage/v2/storage_error.hpp"
 #include "utils/thread.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 const uint64_t kNumVerifiers = 5;
 const uint64_t kNumMutators = 1;
 
@@ -31,7 +33,7 @@ TEST(Storage, LabelIndex) {
 
   auto label = store->NameToLabel("label");
   {
-    auto unique_acc = store->UniqueAccess();
+    auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(label).HasError());
   }
 
@@ -44,7 +46,7 @@ TEST(Storage, LabelIndex) {
       gids.reserve(kNumIterations * kVerifierBatchSize);
       for (uint64_t i = 0; i < kNumIterations; ++i) {
         for (uint64_t j = 0; j < kVerifierBatchSize; ++j) {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertex = acc->CreateVertex();
           gids.emplace(vertex.Gid(), false);
           auto ret = vertex.AddLabel(label);
@@ -53,7 +55,7 @@ TEST(Storage, LabelIndex) {
           ASSERT_FALSE(acc->Commit().HasError());
         }
         {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertices = acc->Vertices(label, memgraph::storage::View::OLD);
           for (auto vertex : vertices) {
             auto it = gids.find(vertex.Gid());
@@ -81,7 +83,7 @@ TEST(Storage, LabelIndex) {
       gids.resize(kMutatorBatchSize);
       while (mutators_run.load(std::memory_order_acquire)) {
         for (uint64_t i = 0; i < kMutatorBatchSize; ++i) {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertex = acc->CreateVertex();
           gids[i] = vertex.Gid();
           auto ret = vertex.AddLabel(label);
@@ -90,7 +92,7 @@ TEST(Storage, LabelIndex) {
           ASSERT_FALSE(acc->Commit().HasError());
         }
         for (uint64_t i = 0; i < kMutatorBatchSize; ++i) {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
           ASSERT_TRUE(vertex);
           ASSERT_TRUE(acc->DeleteVertex(&*vertex).HasValue());
@@ -116,7 +118,7 @@ TEST(Storage, LabelPropertyIndex) {
   auto label = store->NameToLabel("label");
   auto prop = store->NameToProperty("prop");
   {
-    auto unique_acc = store->UniqueAccess();
+    auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(label, prop).HasError());
   }
 
@@ -129,7 +131,7 @@ TEST(Storage, LabelPropertyIndex) {
       gids.reserve(kNumIterations * kVerifierBatchSize);
       for (uint64_t i = 0; i < kNumIterations; ++i) {
         for (uint64_t j = 0; j < kVerifierBatchSize; ++j) {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertex = acc->CreateVertex();
           gids.emplace(vertex.Gid(), false);
           {
@@ -145,7 +147,7 @@ TEST(Storage, LabelPropertyIndex) {
           ASSERT_FALSE(acc->Commit().HasError());
         }
         {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertices = acc->Vertices(label, prop, memgraph::storage::View::OLD);
           for (auto vertex : vertices) {
             auto it = gids.find(vertex.Gid());
@@ -173,7 +175,7 @@ TEST(Storage, LabelPropertyIndex) {
       gids.resize(kMutatorBatchSize);
       while (mutators_run.load(std::memory_order_acquire)) {
         for (uint64_t i = 0; i < kMutatorBatchSize; ++i) {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertex = acc->CreateVertex();
           gids[i] = vertex.Gid();
           {
@@ -189,7 +191,7 @@ TEST(Storage, LabelPropertyIndex) {
           ASSERT_FALSE(acc->Commit().HasError());
         }
         for (uint64_t i = 0; i < kMutatorBatchSize; ++i) {
-          auto acc = store->Access();
+          auto acc = store->Access(ReplicationRole::MAIN);
           auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
           ASSERT_TRUE(vertex);
           ASSERT_TRUE(acc->DeleteVertex(&*vertex).HasValue());
diff --git a/tests/concurrent/storage_unique_constraints.cpp b/tests/concurrent/storage_unique_constraints.cpp
index 694249779..dc3b30146 100644
--- a/tests/concurrent/storage_unique_constraints.cpp
+++ b/tests/concurrent/storage_unique_constraints.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,6 +16,8 @@
 #include "storage/v2/constraints/constraints.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 const int kNumThreads = 8;
 
 #define ASSERT_OK(x) ASSERT_FALSE((x).HasError())
@@ -34,7 +36,7 @@ class StorageUniqueConstraints : public ::testing::Test {
 
   void SetUp() override {
     // Create initial vertices.
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     // NOLINTNEXTLINE(modernize-loop-convert)
     for (int i = 0; i < kNumThreads; ++i) {
       auto vertex = acc->CreateVertex();
@@ -55,7 +57,7 @@ void SetProperties(memgraph::storage::Storage *storage, memgraph::storage::Gid g
                    const std::vector<PropertyId> &properties, const std::vector<PropertyValue> &values,
                    bool *commit_status) {
   ASSERT_EQ(properties.size(), values.size());
-  auto acc = storage->Access();
+  auto acc = storage->Access(ReplicationRole::MAIN);
   auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
   ASSERT_TRUE(vertex);
   int value = 0;
@@ -71,7 +73,7 @@ void SetProperties(memgraph::storage::Storage *storage, memgraph::storage::Gid g
 }
 
 void AddLabel(memgraph::storage::Storage *storage, memgraph::storage::Gid gid, LabelId label, bool *commit_status) {
-  auto acc = storage->Access();
+  auto acc = storage->Access(ReplicationRole::MAIN);
   auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
   ASSERT_TRUE(vertex);
   for (int iter = 0; iter < 40000; ++iter) {
@@ -84,7 +86,7 @@ void AddLabel(memgraph::storage::Storage *storage, memgraph::storage::Gid gid, L
 
 TEST_F(StorageUniqueConstraints, ChangeProperties) {
   {
-    auto unique_acc = storage->UniqueAccess();
+    auto unique_acc = storage->UniqueAccess(ReplicationRole::MAIN);
     auto res = unique_acc->CreateUniqueConstraint(label, {prop1, prop2, prop3});
     ASSERT_TRUE(res.HasValue());
     ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS);
@@ -92,7 +94,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) {
   }
 
   {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     // NOLINTNEXTLINE(modernize-loop-convert)
     for (int i = 0; i < kNumThreads; ++i) {
       auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
@@ -168,7 +170,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) {
 
 TEST_F(StorageUniqueConstraints, ChangeLabels) {
   {
-    auto unique_acc = storage->UniqueAccess();
+    auto unique_acc = storage->UniqueAccess(ReplicationRole::MAIN);
     auto res = unique_acc->CreateUniqueConstraint(label, {prop1, prop2, prop3});
     ASSERT_TRUE(res.HasValue());
     ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS);
@@ -181,7 +183,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) {
   // succeed, as the others should result with constraint violation.
 
   {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     // NOLINTNEXTLINE(modernize-loop-convert)
     for (int i = 0; i < kNumThreads; ++i) {
       auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
@@ -196,7 +198,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) {
   for (int iter = 0; iter < 20; ++iter) {
     // Clear labels.
     {
-      auto acc = storage->Access();
+      auto acc = storage->Access(ReplicationRole::MAIN);
       // NOLINTNEXTLINE(modernize-loop-convert)
       for (int i = 0; i < kNumThreads; ++i) {
         auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
@@ -227,7 +229,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) {
   // should succeed.
 
   {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     // NOLINTNEXTLINE(modernize-loop-convert)
     for (int i = 0; i < kNumThreads; ++i) {
       auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
@@ -242,7 +244,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) {
   for (int iter = 0; iter < 20; ++iter) {
     // Clear labels.
     {
-      auto acc = storage->Access();
+      auto acc = storage->Access(ReplicationRole::MAIN);
       // NOLINTNEXTLINE(modernize-loop-convert)
       for (int i = 0; i < kNumThreads; ++i) {
         auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt
index fcf7f45b6..a95297301 100644
--- a/tests/e2e/CMakeLists.txt
+++ b/tests/e2e/CMakeLists.txt
@@ -77,6 +77,15 @@ add_subdirectory(query_modules_storage_modes)
 add_subdirectory(garbage_collection)
 add_subdirectory(query_planning)
 
+if (MG_EXPERIMENTAL_HIGH_AVAILABILITY)
+  add_subdirectory(high_availability_experimental)
+endif ()
+
+
+if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
+  add_subdirectory(replication_experimental)
+endif ()
+
 copy_e2e_python_files(pytest_runner pytest_runner.sh "")
 copy_e2e_python_files(x x.sh "")
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/tests/e2e/analytical_mode/CMakeLists.txt b/tests/e2e/analytical_mode/CMakeLists.txt
index e22830770..1756c9980 100644
--- a/tests/e2e/analytical_mode/CMakeLists.txt
+++ b/tests/e2e/analytical_mode/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_analytical_mode_e2e_python_files(common.py)
 copy_analytical_mode_e2e_python_files(free_memory.py)
+
+copy_e2e_files(analytical_mode workloads.yaml)
diff --git a/tests/e2e/analyze_graph/CMakeLists.txt b/tests/e2e/analyze_graph/CMakeLists.txt
index 1b96eb960..0faa37caa 100644
--- a/tests/e2e/analyze_graph/CMakeLists.txt
+++ b/tests/e2e/analyze_graph/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_analyze_graph_e2e_python_files(common.py)
 copy_analyze_graph_e2e_python_files(optimize_indexes.py)
+
+copy_e2e_files(analyze_graph workloads.yaml)
diff --git a/tests/e2e/batched_procedures/CMakeLists.txt b/tests/e2e/batched_procedures/CMakeLists.txt
index 19aacb15c..28eb46bee 100644
--- a/tests/e2e/batched_procedures/CMakeLists.txt
+++ b/tests/e2e/batched_procedures/CMakeLists.txt
@@ -7,3 +7,5 @@ copy_batched_procedures_e2e_python_files(conftest.py)
 copy_batched_procedures_e2e_python_files(simple_read.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(batched_procedures workloads.yaml)
diff --git a/tests/e2e/concurrent_query_modules/CMakeLists.txt b/tests/e2e/concurrent_query_modules/CMakeLists.txt
index 6c92387cb..9f3996585 100644
--- a/tests/e2e/concurrent_query_modules/CMakeLists.txt
+++ b/tests/e2e/concurrent_query_modules/CMakeLists.txt
@@ -6,3 +6,5 @@ copy_concurrent_query_modules_e2e_python_files(client.py)
 copy_concurrent_query_modules_e2e_python_files(con_query_modules.py)
 
 add_subdirectory(test_query_modules)
+
+copy_e2e_files(concurrent_query_modules workloads.yaml)
diff --git a/tests/e2e/configuration/CMakeLists.txt b/tests/e2e/configuration/CMakeLists.txt
index 0411c70e3..b02f69639 100644
--- a/tests/e2e/configuration/CMakeLists.txt
+++ b/tests/e2e/configuration/CMakeLists.txt
@@ -1,7 +1,9 @@
 function(copy_configuration_check_e2e_python_files FILE_NAME)
-    copy_e2e_python_files(write_procedures ${FILE_NAME})
+    copy_e2e_python_files(configuration ${FILE_NAME})
 endfunction()
 
 copy_configuration_check_e2e_python_files(default_config.py)
 copy_configuration_check_e2e_python_files(configuration_check.py)
 copy_configuration_check_e2e_python_files(storage_info.py)
+
+copy_e2e_files(configuration workloads.yaml)
diff --git a/tests/e2e/configuration/default_config.py b/tests/e2e/configuration/default_config.py
index e1d42b443..915a14d14 100644
--- a/tests/e2e/configuration/default_config.py
+++ b/tests/e2e/configuration/default_config.py
@@ -66,6 +66,8 @@ startup_config_dict = {
         "Time in seconds after which inactive Bolt sessions will be closed.",
     ),
     "cartesian_product_enabled": ("true", "true", "Enable cartesian product expansion."),
+    "coordinator": ("false", "false", "Controls whether the instance is a replication coordinator."),
+    "coordinator_server_port": ("0", "0", "Port on which coordinator servers will be started."),
     "data_directory": ("mg_data", "mg_data", "Path to directory in which to save all permanent data."),
     "data_recovery_on_startup": (
         "false",
@@ -143,6 +145,7 @@ startup_config_dict = {
         "The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: The MAIN instance allocates a new thread for each REPLICA.",
     ),
     "storage_gc_cycle_sec": ("30", "30", "Storage garbage collector interval (in seconds)."),
+    "storage_python_gc_cycle_sec": ("180", "180", "Storage python full garbage collection interval (in seconds)."),
     "storage_items_per_batch": (
         "1000000",
         "1000000",
@@ -173,11 +176,6 @@ startup_config_dict = {
         "Default storage mode Memgraph uses. Allowed values: IN_MEMORY_TRANSACTIONAL, IN_MEMORY_ANALYTICAL, ON_DISK_TRANSACTIONAL",
     ),
     "storage_wal_file_size_kib": ("20480", "20480", "Minimum file size of each WAL file."),
-    "storage_delete_on_drop": (
-        "true",
-        "true",
-        "If set to true the query 'DROP DATABASE x' will delete the underlying storage as well.",
-    ),
     "stream_transaction_conflict_retries": (
         "30",
         "30",
diff --git a/tests/e2e/configuration/storage_info.py b/tests/e2e/configuration/storage_info.py
index 042a57b08..8af294eca 100644
--- a/tests/e2e/configuration/storage_info.py
+++ b/tests/e2e/configuration/storage_info.py
@@ -11,7 +11,6 @@
 
 import sys
 
-import default_config
 import mgclient
 import pytest
 
diff --git a/tests/e2e/constraints/CMakeLists.txt b/tests/e2e/constraints/CMakeLists.txt
index 0c4ff72d9..b099dab4d 100644
--- a/tests/e2e/constraints/CMakeLists.txt
+++ b/tests/e2e/constraints/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_constraint_validation_e2e_python_files(common.py)
 copy_constraint_validation_e2e_python_files(constraints_validation.py)
+
+copy_e2e_files(constraint_validation workloads.yaml)
diff --git a/tests/e2e/disk_storage/CMakeLists.txt b/tests/e2e/disk_storage/CMakeLists.txt
index 777277178..5e0822ef8 100644
--- a/tests/e2e/disk_storage/CMakeLists.txt
+++ b/tests/e2e/disk_storage/CMakeLists.txt
@@ -13,3 +13,5 @@ copy_disk_storage_e2e_python_files(snapshot_disabled.py)
 copy_disk_storage_e2e_python_files(lock_data_dir_disabled.py)
 copy_disk_storage_e2e_python_files(create_edge_from_indices.py)
 copy_disk_storage_e2e_python_files(storage_info.py)
+
+copy_e2e_files(disk_storage workloads.yaml)
diff --git a/tests/e2e/fine_grained_access/CMakeLists.txt b/tests/e2e/fine_grained_access/CMakeLists.txt
index 6b277694f..71a02cd4b 100644
--- a/tests/e2e/fine_grained_access/CMakeLists.txt
+++ b/tests/e2e/fine_grained_access/CMakeLists.txt
@@ -7,3 +7,5 @@ copy_fine_grained_access_e2e_python_files(create_delete_filtering_tests.py)
 copy_fine_grained_access_e2e_python_files(edge_type_filtering_tests.py)
 copy_fine_grained_access_e2e_python_files(path_filtering_tests.py)
 copy_fine_grained_access_e2e_python_files(show_db.py)
+
+copy_e2e_files(fine_grained_access workloads.yaml)
diff --git a/tests/e2e/fine_grained_access/show_db.py b/tests/e2e/fine_grained_access/show_db.py
index 546d4e24a..c5378dca6 100644
--- a/tests/e2e/fine_grained_access/show_db.py
+++ b/tests/e2e/fine_grained_access/show_db.py
@@ -23,13 +23,20 @@ def test_show_databases_w_user():
     user3_connection = common.connect(username="user3", password="test")
 
     assert common.execute_and_fetch_all(admin_connection.cursor(), "SHOW DATABASES") == [
-        ("db1", ""),
-        ("db2", ""),
-        ("memgraph", "*"),
+        ("db1",),
+        ("db2",),
+        ("memgraph",),
     ]
-    assert common.execute_and_fetch_all(user_connection.cursor(), "SHOW DATABASES") == [("db1", ""), ("memgraph", "*")]
-    assert common.execute_and_fetch_all(user2_connection.cursor(), "SHOW DATABASES") == [("db2", "*")]
-    assert common.execute_and_fetch_all(user3_connection.cursor(), "SHOW DATABASES") == [("db1", "*"), ("db2", "")]
+    assert common.execute_and_fetch_all(admin_connection.cursor(), "SHOW DATABASE") == [("memgraph",)]
+
+    assert common.execute_and_fetch_all(user_connection.cursor(), "SHOW DATABASES") == [("db1",), ("memgraph",)]
+    assert common.execute_and_fetch_all(user_connection.cursor(), "SHOW DATABASE") == [("memgraph",)]
+
+    assert common.execute_and_fetch_all(user2_connection.cursor(), "SHOW DATABASES") == [("db2",)]
+    assert common.execute_and_fetch_all(user2_connection.cursor(), "SHOW DATABASE") == [("db2",)]
+
+    assert common.execute_and_fetch_all(user3_connection.cursor(), "SHOW DATABASES") == [("db1",), ("db2",)]
+    assert common.execute_and_fetch_all(user3_connection.cursor(), "SHOW DATABASE") == [("db1",)]
 
 
 if __name__ == "__main__":
diff --git a/tests/e2e/garbage_collection/CMakeLists.txt b/tests/e2e/garbage_collection/CMakeLists.txt
index 690edf344..e515247ec 100644
--- a/tests/e2e/garbage_collection/CMakeLists.txt
+++ b/tests/e2e/garbage_collection/CMakeLists.txt
@@ -5,3 +5,5 @@ endfunction()
 garbage_collection_e2e_python_files(common.py)
 garbage_collection_e2e_python_files(conftest.py)
 garbage_collection_e2e_python_files(gc_periodic.py)
+
+copy_e2e_files(garbage_collection workloads.yaml)
diff --git a/tests/e2e/garbage_collection/gc_periodic.py b/tests/e2e/garbage_collection/gc_periodic.py
index a93846a68..2f1c8850c 100644
--- a/tests/e2e/garbage_collection/gc_periodic.py
+++ b/tests/e2e/garbage_collection/gc_periodic.py
@@ -38,21 +38,21 @@ def get_memory(cursor):
 
 
 def test_gc_periodic(connection):
-    """
-    This test checks that periodic gc works.
-    It does so by checking that the allocated memory is lowered by at least 1/4 of the memory allocated by creating nodes.
-    If we choose a number a high number the test will become flaky because the memory only gets fully cleared after a while
-    due to jemalloc holding some memory for a while. If we'd wait for jemalloc to fully release the memory the test would take too long.
-    """
     cursor = connection.cursor()
 
     memory_pre_creation = get_memory(cursor)
-    execute_and_fetch_all(cursor, "UNWIND range(1, 1000) AS index CREATE (:Node);")
+    execute_and_fetch_all(cursor, "UNWIND range(1, 100000) AS index CREATE (:Node);")
     memory_after_creation = get_memory(cursor)
-    time.sleep(5)
-    memory_after_gc = get_memory(cursor)
+    execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
 
-    assert memory_after_gc < memory_pre_creation + (memory_after_creation - memory_pre_creation) / 4 * 3
+    for i in range(5):
+        time.sleep(2)
+        memory_after_gc = get_memory(cursor)
+
+        if memory_after_gc < memory_pre_creation + (memory_after_creation - memory_pre_creation) / 4:
+            return
+
+    raise MemoryError("GC didn't clean the memory after 10 seconds")
 
 
 if __name__ == "__main__":
diff --git a/tests/e2e/graphql/CMakeLists.txt b/tests/e2e/graphql/CMakeLists.txt
index 7ad1624b6..384d534d8 100644
--- a/tests/e2e/graphql/CMakeLists.txt
+++ b/tests/e2e/graphql/CMakeLists.txt
@@ -8,3 +8,5 @@ copy_graphql_e2e_python_files(callable_alias_mapping.json)
 
 add_subdirectory(graphql_library_config)
 add_subdirectory(temporary_procedures)
+
+copy_e2e_files(graphql workloads.yaml)
diff --git a/tests/e2e/high_availability_experimental/CMakeLists.txt b/tests/e2e/high_availability_experimental/CMakeLists.txt
new file mode 100644
index 000000000..76e1a6956
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/CMakeLists.txt
@@ -0,0 +1,12 @@
+find_package(gflags REQUIRED)
+
+copy_e2e_python_files(ha_experimental coordinator.py)
+copy_e2e_python_files(ha_experimental automatic_failover.py)
+copy_e2e_python_files(ha_experimental manual_setting_replicas.py)
+copy_e2e_python_files(ha_experimental common.py)
+copy_e2e_python_files(ha_experimental conftest.py)
+copy_e2e_python_files(ha_experimental workloads.yaml)
+
+copy_e2e_python_files_from_parent_folder(ha_experimental ".." memgraph.py)
+copy_e2e_python_files_from_parent_folder(ha_experimental ".." interactive_mg_runner.py)
+copy_e2e_python_files_from_parent_folder(ha_experimental ".." mg_utils.py)
diff --git a/tests/e2e/high_availability_experimental/automatic_failover.py b/tests/e2e/high_availability_experimental/automatic_failover.py
new file mode 100644
index 000000000..f3ffadfe8
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/automatic_failover.py
@@ -0,0 +1,223 @@
+# Copyright 2024 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import os
+import sys
+
+import interactive_mg_runner
+import pytest
+from common import execute_and_fetch_all
+from mg_utils import mg_sleep_and_assert
+
+interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+interactive_mg_runner.PROJECT_DIR = os.path.normpath(
+    os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..")
+)
+interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build"))
+interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph"))
+
+MEMGRAPH_INSTANCES_DESCRIPTION = {
+    "instance_1": {
+        "args": ["--bolt-port", "7688", "--log-level", "TRACE", "--coordinator-server-port", "10011"],
+        "log_file": "replica1.log",
+        "setup_queries": [],
+    },
+    "instance_2": {
+        "args": ["--bolt-port", "7689", "--log-level", "TRACE", "--coordinator-server-port", "10012"],
+        "log_file": "replica2.log",
+        "setup_queries": [],
+    },
+    "instance_3": {
+        "args": ["--bolt-port", "7687", "--log-level", "TRACE", "--coordinator-server-port", "10013"],
+        "log_file": "main.log",
+        "setup_queries": [],
+    },
+    "coordinator": {
+        "args": ["--bolt-port", "7690", "--log-level=TRACE", "--coordinator"],
+        "log_file": "replica3.log",
+        "setup_queries": [
+            "REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001';",
+            "REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002';",
+            "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003';",
+            "SET INSTANCE instance_3 TO MAIN",
+        ],
+    },
+}
+
+
+def test_replication_works_on_failover(connection):
+    # Goal of this test is to check the replication works after failover command.
+    # 1. We start all replicas, main and coordinator manually: we want to be able to kill them ourselves without relying on external tooling to kill processes.
+    # 2. We check that main has correct state
+    # 3. We kill main
+    # 4. We check that coordinator and new main have correct state
+    # 5. We insert one vertex on new main
+    # 6. We check that vertex appears on new replica
+
+    # 1
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    # 2
+    main_cursor = connection(7687, "instance_3").cursor()
+    expected_data_on_main = [
+        ("instance_1", "127.0.0.1:10001", "sync", 0, 0, "ready"),
+        ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
+    ]
+    actual_data_on_main = sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS;")))
+    assert actual_data_on_main == expected_data_on_main
+
+    # 3
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
+
+    # 4
+    coord_cursor = connection(7690, "coordinator").cursor()
+
+    def retrieve_data_show_repl_cluster():
+        return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW REPLICATION CLUSTER;")))
+
+    expected_data_on_coord = [
+        ("instance_1", "127.0.0.1:10011", True, "main"),
+        ("instance_2", "127.0.0.1:10012", True, "replica"),
+        ("instance_3", "127.0.0.1:10013", False, ""),
+    ]
+    mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_repl_cluster)
+
+    new_main_cursor = connection(7688, "instance_1").cursor()
+
+    def retrieve_data_show_replicas():
+        return sorted(list(execute_and_fetch_all(new_main_cursor, "SHOW REPLICAS;")))
+
+    expected_data_on_new_main = [
+        ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
+    ]
+    mg_sleep_and_assert(expected_data_on_new_main, retrieve_data_show_replicas)
+
+    # 5
+    execute_and_fetch_all(new_main_cursor, "CREATE ();")
+
+    # 6
+    alive_replica_cursror = connection(7689, "instance_2").cursor()
+    res = execute_and_fetch_all(alive_replica_cursror, "MATCH (n) RETURN count(n) as count;")[0][0]
+    assert res == 1, "Vertex should be replicated"
+    interactive_mg_runner.stop_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+
+def test_show_replication_cluster(connection):
+    # Goal of this test is to check the SHOW REPLICATION CLUSTER command.
+    # 1. We start all replicas, main and coordinator manually: we want to be able to kill them ourselves without relying on external tooling to kill processes.
+    # 2. We check that all replicas and main have the correct state: they should all be alive.
+    # 3. We kill one replica. It should not appear anymore in the SHOW REPLICATION CLUSTER command.
+    # 4. We kill main. It should not appear anymore in the SHOW REPLICATION CLUSTER command.
+
+    # 1.
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    cursor = connection(7690, "coordinator").cursor()
+
+    # 2.
+
+    # We leave some time for the coordinator to realise the replicas are down.
+    def retrieve_data():
+        return sorted(list(execute_and_fetch_all(cursor, "SHOW REPLICATION CLUSTER;")))
+
+    expected_data = [
+        ("instance_1", "127.0.0.1:10011", True, "replica"),
+        ("instance_2", "127.0.0.1:10012", True, "replica"),
+        ("instance_3", "127.0.0.1:10013", True, "main"),
+    ]
+    mg_sleep_and_assert(expected_data, retrieve_data)
+
+    # 3.
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_1")
+
+    expected_data = [
+        ("instance_1", "127.0.0.1:10011", False, ""),
+        ("instance_2", "127.0.0.1:10012", True, "replica"),
+        ("instance_3", "127.0.0.1:10013", True, "main"),
+    ]
+    mg_sleep_and_assert(expected_data, retrieve_data)
+
+    # 4.
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_2")
+
+    expected_data = [
+        ("instance_1", "127.0.0.1:10011", False, ""),
+        ("instance_2", "127.0.0.1:10012", False, ""),
+        ("instance_3", "127.0.0.1:10013", True, "main"),
+    ]
+    mg_sleep_and_assert(expected_data, retrieve_data)
+
+
+def test_simple_automatic_failover(connection):
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    main_cursor = connection(7687, "instance_3").cursor()
+    expected_data_on_main = [
+        ("instance_1", "127.0.0.1:10001", "sync", 0, 0, "ready"),
+        ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
+    ]
+    actual_data_on_main = sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS;")))
+    assert actual_data_on_main == expected_data_on_main
+
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
+
+    coord_cursor = connection(7690, "coordinator").cursor()
+
+    def retrieve_data_show_repl_cluster():
+        return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW REPLICATION CLUSTER;")))
+
+    expected_data_on_coord = [
+        ("instance_1", "127.0.0.1:10011", True, "main"),
+        ("instance_2", "127.0.0.1:10012", True, "replica"),
+        ("instance_3", "127.0.0.1:10013", False, ""),
+    ]
+    mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_repl_cluster)
+
+    new_main_cursor = connection(7688, "instance_1").cursor()
+
+    def retrieve_data_show_replicas():
+        return sorted(list(execute_and_fetch_all(new_main_cursor, "SHOW REPLICAS;")))
+
+    expected_data_on_new_main = [
+        ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
+    ]
+    mg_sleep_and_assert(expected_data_on_new_main, retrieve_data_show_replicas)
+
+
+def test_registering_replica_fails_name_exists(connection):
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    coord_cursor = connection(7690, "coordinator").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(
+            coord_cursor,
+            "REGISTER INSTANCE instance_1 ON '127.0.0.1:10051' WITH '127.0.0.1:10111';",
+        )
+    assert str(e.value) == "Couldn't register replica instance since instance with such name already exists!"
+
+
+def test_registering_replica_fails_endpoint_exists(connection):
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    coord_cursor = connection(7690, "coordinator").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(
+            coord_cursor,
+            "REGISTER INSTANCE instance_5 ON '127.0.0.1:10001' WITH '127.0.0.1:10013';",
+        )
+    assert (
+        str(e.value)
+        == "Couldn't register replica because promotion on replica failed! Check logs on replica to find out more info!"
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/high_availability_experimental/common.py b/tests/e2e/high_availability_experimental/common.py
new file mode 100644
index 000000000..dc104d628
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/common.py
@@ -0,0 +1,25 @@
+# Copyright 2022 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import typing
+
+import mgclient
+
+
+def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
+    cursor.execute(query, params)
+    return cursor.fetchall()
+
+
+def connect(**kwargs) -> mgclient.Connection:
+    connection = mgclient.connect(**kwargs)
+    connection.autocommit = True
+    return connection
diff --git a/tests/e2e/high_availability_experimental/conftest.py b/tests/e2e/high_availability_experimental/conftest.py
new file mode 100644
index 000000000..9100a63cf
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/conftest.py
@@ -0,0 +1,43 @@
+# Copyright 2022 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import pytest
+from common import connect, execute_and_fetch_all
+
+
+# The fixture here is more complex because the connection has to be
+# parameterized based on the test parameters (info has to be available on both
+# sides).
+#
+# https://docs.pytest.org/en/latest/example/parametrize.html#indirect-parametrization
+# is not an elegant/feasible solution here.
+#
+# The solution was independently developed and then I stumbled upon the same
+# approach here https://stackoverflow.com/a/68286553/4888809 which I think is
+# optimal.
+@pytest.fixture(scope="function")
+def connection():
+    connection_holder = None
+    role_holder = None
+
+    def inner_connection(port, role):
+        nonlocal connection_holder, role_holder
+        connection_holder = connect(host="localhost", port=port)
+        role_holder = role
+        return connection_holder
+
+    yield inner_connection
+
+    # Only main instance can be cleaned up because replicas do NOT accept
+    # writes.
+    if role_holder == "main":
+        cursor = connection_holder.cursor()
+        execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
diff --git a/tests/e2e/high_availability_experimental/coordinator.py b/tests/e2e/high_availability_experimental/coordinator.py
new file mode 100644
index 000000000..e34e9f069
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/coordinator.py
@@ -0,0 +1,87 @@
+# Copyright 2024 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import sys
+
+import pytest
+from common import execute_and_fetch_all
+from mg_utils import mg_sleep_and_assert
+
+
+def test_disable_cypher_queries(connection):
+    cursor = connection(7690, "coordinator").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(cursor, "CREATE (n:TestNode {prop: 'test'})")
+    assert str(e.value) == "Coordinator can run only coordinator queries!"
+
+
+def test_coordinator_cannot_be_replica_role(connection):
+    cursor = connection(7690, "coordinator").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
+    assert str(e.value) == "Coordinator can run only coordinator queries!"
+
+
+def test_coordinator_cannot_run_show_repl_role(connection):
+    cursor = connection(7690, "coordinator").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(cursor, "SHOW REPLICATION ROLE;")
+    assert str(e.value) == "Coordinator can run only coordinator queries!"
+
+
+def test_coordinator_show_replication_cluster(connection):
+    cursor = connection(7690, "coordinator").cursor()
+
+    def retrieve_data():
+        return sorted(list(execute_and_fetch_all(cursor, "SHOW REPLICATION CLUSTER;")))
+
+    expected_data = [
+        ("instance_1", "127.0.0.1:10011", True, "replica"),
+        ("instance_2", "127.0.0.1:10012", True, "replica"),
+        ("instance_3", "127.0.0.1:10013", True, "main"),
+    ]
+    mg_sleep_and_assert(expected_data, retrieve_data)
+
+
+def test_coordinator_cannot_call_show_replicas(connection):
+    cursor = connection(7690, "coordinator").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(cursor, "SHOW REPLICAS;")
+    assert str(e.value) == "Coordinator can run only coordinator queries!"
+
+
+@pytest.mark.parametrize(
+    "port, role",
+    [(7687, "main"), (7688, "replica"), (7689, "replica")],
+)
+def test_main_and_replicas_cannot_call_show_repl_cluster(port, role, connection):
+    cursor = connection(port, role).cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(cursor, "SHOW REPLICATION CLUSTER;")
+    assert str(e.value) == "Only coordinator can run SHOW REPLICATION CLUSTER."
+
+
+@pytest.mark.parametrize(
+    "port, role",
+    [(7687, "main"), (7688, "replica"), (7689, "replica")],
+)
+def test_main_and_replicas_cannot_register_coord_server(port, role, connection):
+    cursor = connection(port, role).cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(
+            cursor,
+            "REGISTER INSTANCE instance_1 ON '127.0.0.1:10001' WITH '127.0.0.1:10011';",
+        )
+    assert str(e.value) == "Only coordinator can register coordinator server!"
+
+
+if __name__ == "__main__":
+    sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/high_availability_experimental/manual_setting_replicas.py b/tests/e2e/high_availability_experimental/manual_setting_replicas.py
new file mode 100644
index 000000000..f2d48ffd7
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/manual_setting_replicas.py
@@ -0,0 +1,57 @@
+# Copyright 2024 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import os
+import sys
+
+import interactive_mg_runner
+import pytest
+from common import execute_and_fetch_all
+from mg_utils import mg_sleep_and_assert
+
+interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+interactive_mg_runner.PROJECT_DIR = os.path.normpath(
+    os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..")
+)
+interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build"))
+interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph"))
+
+MEMGRAPH_INSTANCES_DESCRIPTION = {
+    "instance_3": {
+        "args": ["--bolt-port", "7687", "--log-level", "TRACE", "--coordinator-server-port", "10013"],
+        "log_file": "main.log",
+        "setup_queries": [],
+    },
+}
+
+
+def test_no_manual_setup_on_main(connection):
+    # Goal of this test is to check that all manual registration actions are disabled on instances with coordiantor server port
+
+    # 1
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    any_main = connection(7687, "instance_3").cursor()
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(any_main, "REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:10001';")
+    assert str(e.value) == "Can't register replica manually on instance with coordinator server port."
+
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(any_main, "DROP REPLICA replica_1;")
+    assert str(e.value) == "Can't drop replica manually on instance with coordinator server port."
+
+    with pytest.raises(Exception) as e:
+        execute_and_fetch_all(any_main, "SET REPLICATION ROLE TO REPLICA WITH PORT 10002;")
+    assert str(e.value) == "Can't set role manually on instance with coordinator server port."
+
+
+if __name__ == "__main__":
+    sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/high_availability_experimental/workloads.yaml b/tests/e2e/high_availability_experimental/workloads.yaml
new file mode 100644
index 000000000..1d692084a
--- /dev/null
+++ b/tests/e2e/high_availability_experimental/workloads.yaml
@@ -0,0 +1,37 @@
+ha_cluster: &ha_cluster
+  cluster:
+    replica_1:
+      args: ["--bolt-port", "7688", "--log-level=TRACE", "--coordinator-server-port=10011"]
+      log_file: "replication-e2e-replica1.log"
+      setup_queries: []
+    replica_2:
+      args: ["--bolt-port", "7689", "--log-level=TRACE", "--coordinator-server-port=10012"]
+      log_file: "replication-e2e-replica2.log"
+      setup_queries: []
+    main:
+      args: ["--bolt-port", "7687", "--log-level=TRACE", "--coordinator-server-port=10013"]
+      log_file: "replication-e2e-main.log"
+      setup_queries: []
+    coordinator:
+      args: ["--bolt-port", "7690", "--log-level=TRACE", "--coordinator"]
+      log_file: "replication-e2e-coordinator.log"
+      setup_queries: [
+        "REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001';",
+        "REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002';",
+        "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003';",
+        "SET INSTANCE instance_3 TO MAIN;"
+      ]
+
+workloads:
+  - name: "Coordinator"
+    binary: "tests/e2e/pytest_runner.sh"
+    args: ["high_availability_experimental/coordinator.py"]
+    <<: *ha_cluster
+
+  - name: "Automatic failover"
+    binary: "tests/e2e/pytest_runner.sh"
+    args: ["high_availability_experimental/automatic_failover.py"]
+
+  - name: "Disabled manual setting of replication cluster"
+    binary: "tests/e2e/pytest_runner.sh"
+    args: ["high_availability_experimental/manual_setting_replicas.py"]
diff --git a/tests/e2e/import_mode/CMakeLists.txt b/tests/e2e/import_mode/CMakeLists.txt
index e316b7e82..b48b4f12d 100644
--- a/tests/e2e/import_mode/CMakeLists.txt
+++ b/tests/e2e/import_mode/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_import_mode_e2e_python_files(common.py)
 copy_import_mode_e2e_python_files(test_command.py)
+
+copy_e2e_files(import_mode workloads.yaml)
diff --git a/tests/e2e/index_hints/CMakeLists.txt b/tests/e2e/index_hints/CMakeLists.txt
index 5261baacc..38b3baef0 100644
--- a/tests/e2e/index_hints/CMakeLists.txt
+++ b/tests/e2e/index_hints/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_index_hints_e2e_python_files(common.py)
 copy_index_hints_e2e_python_files(index_hints.py)
+
+copy_e2e_files(index_hints workloads.yaml)
diff --git a/tests/e2e/index_hints/index_hints.py b/tests/e2e/index_hints/index_hints.py
index 70d3ce6b6..b59d60103 100644
--- a/tests/e2e/index_hints/index_hints.py
+++ b/tests/e2e/index_hints/index_hints.py
@@ -478,5 +478,44 @@ def test_nonexistent_label_property_index(memgraph):
         assert False
 
 
+def test_index_hint_on_expand(memgraph):
+    # Prefer expanding from the node with the given hint even if estimator estimates higher cost for that plan
+
+    memgraph.execute("FOREACH (i IN range(1, 1000) | CREATE (n:Label1 {id: i}));")
+    memgraph.execute("FOREACH (i IN range(1, 10) | CREATE (n:Label2 {id: i}));")
+    memgraph.execute("CREATE INDEX ON :Label1;")
+    memgraph.execute("CREATE INDEX ON :Label2;")
+
+    expected_explain_without_hint = [
+        " * Produce {n, m}",
+        " * Filter (n :Label1)",
+        " * Expand (m)<-[anon1:rel]-(n)",
+        " * ScanAllByLabel (m :Label2)",
+        " * Once",
+    ]
+
+    expected_explain_with_hint = [
+        " * Produce {n, m}",
+        " * Filter (m :Label2)",
+        " * Expand (n)-[anon1:rel]->(m)",
+        " * ScanAllByLabel (n :Label1)",
+        " * Once",
+    ]
+
+    explain_without_hint = [
+        row["QUERY PLAN"]
+        for row in memgraph.execute_and_fetch("EXPLAIN MATCH (n:Label1)-[:rel]->(m:Label2) RETURN n, m;")
+    ]
+
+    explain_with_hint = [
+        row["QUERY PLAN"]
+        for row in memgraph.execute_and_fetch(
+            "EXPLAIN USING INDEX :Label1 MATCH (n:Label1)-[:rel]->(m:Label2) RETURN n, m;"
+        )
+    ]
+
+    assert explain_without_hint == expected_explain_without_hint and explain_with_hint == expected_explain_with_hint
+
+
 if __name__ == "__main__":
     sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/init_file_flags/CMakeLists.txt b/tests/e2e/init_file_flags/CMakeLists.txt
index 8d98898e8..50ac5e8e5 100644
--- a/tests/e2e/init_file_flags/CMakeLists.txt
+++ b/tests/e2e/init_file_flags/CMakeLists.txt
@@ -10,3 +10,5 @@ copy_init_file_flags_e2e_python_files(init_file_setup.py)
 copy_init_file_flags_e2e_python_files(init_data_file_setup.py)
 
 copy_init_file_flags_e2e_files(init_file.cypherl)
+
+copy_e2e_files(init_file_flags workloads.yaml)
diff --git a/tests/e2e/inspect_query/CMakeLists.txt b/tests/e2e/inspect_query/CMakeLists.txt
index f0dbdb7cc..4b9b3d82d 100644
--- a/tests/e2e/inspect_query/CMakeLists.txt
+++ b/tests/e2e/inspect_query/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_inspect_query_e2e_python_files(common.py)
 copy_inspect_query_e2e_python_files(inspect_query.py)
+
+copy_e2e_files(inspect_query workloads.yaml)
diff --git a/tests/e2e/interactive_mg_runner.py b/tests/e2e/interactive_mg_runner.py
old mode 100644
new mode 100755
index 13aa951db..f0e4e6da1
--- a/tests/e2e/interactive_mg_runner.py
+++ b/tests/e2e/interactive_mg_runner.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
 # Copyright 2022 Memgraph Ltd.
 #
 # Use of this software is governed by the Business Source License
diff --git a/tests/e2e/isolation_levels/CMakeLists.txt b/tests/e2e/isolation_levels/CMakeLists.txt
index a5f31a79d..1835d75df 100644
--- a/tests/e2e/isolation_levels/CMakeLists.txt
+++ b/tests/e2e/isolation_levels/CMakeLists.txt
@@ -2,3 +2,5 @@ find_package(gflags REQUIRED)
 
 add_executable(memgraph__e2e__isolation_levels isolation_levels.cpp)
 target_link_libraries(memgraph__e2e__isolation_levels gflags mgclient mg-utils mg-io Threads::Threads)
+
+copy_e2e_files(isolation_levels workloads.yaml)
diff --git a/tests/e2e/isolation_levels/isolation_levels.cpp b/tests/e2e/isolation_levels/isolation_levels.cpp
index 2ead05750..751e63594 100644
--- a/tests/e2e/isolation_levels/isolation_levels.cpp
+++ b/tests/e2e/isolation_levels/isolation_levels.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -87,18 +87,13 @@ void SwitchToDB(const std::string &name, std::unique_ptr<mg::Client> &client) {
 void SwitchToCleanDB(std::unique_ptr<mg::Client> &client) { SwitchToDB("clean", client); }
 
 void SwitchToSameDB(std::unique_ptr<mg::Client> &main, std::unique_ptr<mg::Client> &client) {
-  MG_ASSERT(main->Execute("SHOW DATABASES;"));
+  MG_ASSERT(main->Execute("SHOW DATABASE;"));
   auto dbs = main->FetchAll();
   MG_ASSERT(dbs, "Failed to show databases");
-  for (const auto &elem : *dbs) {
-    MG_ASSERT(!elem.empty(), "Show databases wrong output");
-    const auto &active = elem[1].ValueString();
-    if (active == "*") {
-      const auto &name = elem[0].ValueString();
-      SwitchToDB(std::string(name), client);
-      break;
-    }
-  }
+  MG_ASSERT(!dbs->empty(), "Show databases wrong output");
+  MG_ASSERT(!(*dbs)[0].empty(), "Show databases wrong output");
+  const auto &name = (*dbs)[0][0].ValueString();
+  SwitchToDB(std::string(name), client);
 }
 
 void TestSnapshotIsolation(std::unique_ptr<mg::Client> &client) {
diff --git a/tests/e2e/lba_procedures/CMakeLists.txt b/tests/e2e/lba_procedures/CMakeLists.txt
index 8e1ebb41b..9547ef430 100644
--- a/tests/e2e/lba_procedures/CMakeLists.txt
+++ b/tests/e2e/lba_procedures/CMakeLists.txt
@@ -11,3 +11,5 @@ copy_lba_procedures_e2e_python_files(read_permission_queries.py)
 copy_lba_procedures_e2e_python_files(update_permission_queries.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(lba_procedures workloads.yaml)
diff --git a/tests/e2e/lba_procedures/show_privileges.py b/tests/e2e/lba_procedures/show_privileges.py
index 1021ea7be..247140a60 100644
--- a/tests/e2e/lba_procedures/show_privileges.py
+++ b/tests/e2e/lba_procedures/show_privileges.py
@@ -40,6 +40,7 @@ BASIC_PRIVILEGES = [
     "STORAGE_MODE",
     "MULTI_DATABASE_EDIT",
     "MULTI_DATABASE_USE",
+    "COORDINATOR",
 ]
 
 
@@ -63,7 +64,7 @@ def test_lba_procedures_show_privileges_first_user():
     cursor = connect(username="Josip", password="").cursor()
     result = execute_and_fetch_all(cursor, "SHOW PRIVILEGES FOR Josip;")
 
-    assert len(result) == 34
+    assert len(result) == 35
 
     fine_privilege_results = [res for res in result if res[0] not in BASIC_PRIVILEGES]
 
diff --git a/tests/e2e/load_csv/CMakeLists.txt b/tests/e2e/load_csv/CMakeLists.txt
index 368915dbe..6c1ebc38a 100644
--- a/tests/e2e/load_csv/CMakeLists.txt
+++ b/tests/e2e/load_csv/CMakeLists.txt
@@ -11,3 +11,5 @@ copy_load_csv_e2e_files(simple.csv)
 
 copy_load_csv_e2e_python_files(load_csv_nullif.py)
 copy_load_csv_e2e_files(nullif.csv)
+
+copy_e2e_files(load_csv workloads.yaml)
diff --git a/tests/e2e/magic_functions/CMakeLists.txt b/tests/e2e/magic_functions/CMakeLists.txt
index 3ab627e22..0f009f635 100644
--- a/tests/e2e/magic_functions/CMakeLists.txt
+++ b/tests/e2e/magic_functions/CMakeLists.txt
@@ -8,3 +8,5 @@ copy_magic_functions_e2e_python_files(conftest.py)
 copy_magic_functions_e2e_python_files(function_example.py)
 
 add_subdirectory(functions)
+
+copy_e2e_files(functions workloads.yaml)
diff --git a/tests/e2e/memory/CMakeLists.txt b/tests/e2e/memory/CMakeLists.txt
index 3c4cdc279..256107724 100644
--- a/tests/e2e/memory/CMakeLists.txt
+++ b/tests/e2e/memory/CMakeLists.txt
@@ -49,3 +49,5 @@ target_link_libraries(memgraph__e2e__procedure_memory_limit gflags mgclient mg-u
 
 add_executable(memgraph__e2e__procedure_memory_limit_multi_proc procedure_memory_limit_multi_proc.cpp)
 target_link_libraries(memgraph__e2e__procedure_memory_limit_multi_proc gflags mgclient mg-utils mg-io)
+
+copy_e2e_files(memory workloads.yaml)
diff --git a/tests/e2e/mg_utils.py b/tests/e2e/mg_utils.py
index 9eec91da0..74cc8dc3a 100644
--- a/tests/e2e/mg_utils.py
+++ b/tests/e2e/mg_utils.py
@@ -1,12 +1,11 @@
 import time
 
 
-def mg_sleep_and_assert(expected_value, function_to_retrieve_data, max_duration=20, time_between_attempt=0.05):
+def mg_sleep_and_assert(expected_value, function_to_retrieve_data, max_duration=20, time_between_attempt=0.2):
     result = function_to_retrieve_data()
     start_time = time.time()
     while result != expected_value:
-        current_time = time.time()
-        duration = current_time - start_time
+        duration = time.time() - start_time
         if duration > max_duration:
             assert (
                 False
diff --git a/tests/e2e/mock_api/CMakeLists.txt b/tests/e2e/mock_api/CMakeLists.txt
index aa170dc62..ef5845b26 100644
--- a/tests/e2e/mock_api/CMakeLists.txt
+++ b/tests/e2e/mock_api/CMakeLists.txt
@@ -6,3 +6,5 @@ add_subdirectory(procedures)
 
 copy_mock_python_api_e2e_files(common.py)
 copy_mock_python_api_e2e_files(test_compare_mock.py)
+
+copy_e2e_files(mock_python_api workloads.yaml)
diff --git a/tests/e2e/module_file_manager/CMakeLists.txt b/tests/e2e/module_file_manager/CMakeLists.txt
index 84d8845ff..d8eea3f9b 100644
--- a/tests/e2e/module_file_manager/CMakeLists.txt
+++ b/tests/e2e/module_file_manager/CMakeLists.txt
@@ -2,3 +2,5 @@ find_package(gflags REQUIRED)
 
 add_executable(memgraph__e2e__module_file_manager module_file_manager.cpp)
 target_link_libraries(memgraph__e2e__module_file_manager gflags mgclient mg-utils mg-io Threads::Threads)
+
+copy_e2e_files(module_file_manager workloads.yaml)
diff --git a/tests/e2e/monitoring_server/CMakeLists.txt b/tests/e2e/monitoring_server/CMakeLists.txt
index 4c2c441e2..7062e978d 100644
--- a/tests/e2e/monitoring_server/CMakeLists.txt
+++ b/tests/e2e/monitoring_server/CMakeLists.txt
@@ -6,3 +6,5 @@ target_link_libraries(memgraph__e2e__monitoring_server mgclient mg-utils json gf
 
 add_executable(memgraph__e2e__monitoring_server_ssl monitoring_ssl.cpp)
 target_link_libraries(memgraph__e2e__monitoring_server_ssl mgclient mg-utils json gflags Boost::headers)
+
+copy_e2e_files(monitoring_server workloads.yaml)
diff --git a/tests/e2e/python_query_modules_reloading/CMakeLists.txt b/tests/e2e/python_query_modules_reloading/CMakeLists.txt
index ee8f29f90..27320e91b 100644
--- a/tests/e2e/python_query_modules_reloading/CMakeLists.txt
+++ b/tests/e2e/python_query_modules_reloading/CMakeLists.txt
@@ -6,3 +6,5 @@ copy_query_modules_reloading_procedures_e2e_python_files(common.py)
 copy_query_modules_reloading_procedures_e2e_python_files(test_reload_query_module.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(python_query_modules_reloading workloads.yaml)
diff --git a/tests/e2e/queries/CMakeLists.txt b/tests/e2e/queries/CMakeLists.txt
index f672b8591..720599a18 100644
--- a/tests/e2e/queries/CMakeLists.txt
+++ b/tests/e2e/queries/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_queries_e2e_python_files(common.py)
 copy_queries_e2e_python_files(queries.py)
+
+copy_e2e_files(queries workloads.yaml)
diff --git a/tests/e2e/query_modules/CMakeLists.txt b/tests/e2e/query_modules/CMakeLists.txt
index a97bbf1a5..3af2b80b6 100644
--- a/tests/e2e/query_modules/CMakeLists.txt
+++ b/tests/e2e/query_modules/CMakeLists.txt
@@ -7,3 +7,5 @@ copy_query_modules_e2e_python_files(conftest.py)
 copy_query_modules_e2e_python_files(convert_test.py)
 copy_query_modules_e2e_python_files(mgps_test.py)
 copy_query_modules_e2e_python_files(schema_test.py)
+
+copy_e2e_files(query_modules workloads.yaml)
diff --git a/tests/e2e/replication/CMakeLists.txt b/tests/e2e/replication/CMakeLists.txt
index 39f179a3d..4abd10278 100644
--- a/tests/e2e/replication/CMakeLists.txt
+++ b/tests/e2e/replication/CMakeLists.txt
@@ -9,11 +9,13 @@ target_link_libraries(memgraph__e2e__replication__indices gflags mgclient mg-uti
 add_executable(memgraph__e2e__replication__read_write_benchmark read_write_benchmark.cpp)
 target_link_libraries(memgraph__e2e__replication__read_write_benchmark gflags json mgclient mg-utils mg-io Threads::Threads)
 
-copy_e2e_python_files(replication_show common.py)
-copy_e2e_python_files(replication_show conftest.py)
-copy_e2e_python_files(replication_show show.py)
-copy_e2e_python_files(replication_show show_while_creating_invalid_state.py)
-copy_e2e_python_files(replication_show edge_delete.py)
-copy_e2e_python_files_from_parent_folder(replication_show ".." memgraph.py)
-copy_e2e_python_files_from_parent_folder(replication_show ".." interactive_mg_runner.py)
-copy_e2e_python_files_from_parent_folder(replication_show ".." mg_utils.py)
+copy_e2e_python_files(replication common.py)
+copy_e2e_python_files(replication conftest.py)
+copy_e2e_python_files(replication show.py)
+copy_e2e_python_files(replication show_while_creating_invalid_state.py)
+copy_e2e_python_files(replication edge_delete.py)
+copy_e2e_python_files_from_parent_folder(replication ".." memgraph.py)
+copy_e2e_python_files_from_parent_folder(replication ".." interactive_mg_runner.py)
+copy_e2e_python_files_from_parent_folder(replication ".." mg_utils.py)
+
+copy_e2e_files(replication workloads.yaml)
diff --git a/tests/e2e/replication/constraints.cpp b/tests/e2e/replication/constraints.cpp
index 01c1217f2..6f7e2991a 100644
--- a/tests/e2e/replication/constraints.cpp
+++ b/tests/e2e/replication/constraints.cpp
@@ -49,7 +49,7 @@ int main(int argc, char **argv) {
         const auto label_name = (*data)[0][1].ValueString();
         const auto property_name = (*data)[0][2].ValueList()[0].ValueString();
         if (label_name != "Node" || property_name != "id") {
-          LOG_FATAL("{} does NOT hava valid constraint created.", database_endpoint);
+          LOG_FATAL("{} does NOT have a valid constraint created.", database_endpoint);
         }
       } else {
         LOG_FATAL("Unable to get CONSTRAINT INFO from {}", database_endpoint);
diff --git a/tests/e2e/replication/show_while_creating_invalid_state.py b/tests/e2e/replication/show_while_creating_invalid_state.py
index 74dcbce74..8da0c560a 100644
--- a/tests/e2e/replication/show_while_creating_invalid_state.py
+++ b/tests/e2e/replication/show_while_creating_invalid_state.py
@@ -308,7 +308,7 @@ def test_basic_recovery(connection):
                 "--bolt-port",
                 "7687",
                 "--log-level=TRACE",
-                "--storage-recover-on-startup=true",
+                "--data-recovery-on-startup=true",
                 "--replication-restore-state-on-startup=true",
             ],
             "log_file": "main.log",
@@ -1754,5 +1754,74 @@ def test_triggers_on_create_before_commit_with_offline_sync_replica():
     assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
 
 
+def test_replication_not_messed_up_by_CreateSnapshot(connection):
+    # Goal of this test is to check the replica can not run CreateSnapshot
+    # 1/ CREATE SNAPSHOT should raise a DatabaseError
+
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    cursor = connection(7688, "replica_1").cursor()
+
+    # 1/
+    with pytest.raises(mgclient.DatabaseError):
+        execute_and_fetch_all(cursor, "CREATE SNAPSHOT;")
+
+
+def test_replication_not_messed_up_by_ShowIndexInfo(connection):
+    # Goal of this test is to check the replicas timestamp and hence ability to recieve MAINs writes
+    # is uneffected by SHOW INDEX INFO
+
+    # 1/ Run SHOW INDEX INFO; multiple times on REPLICA
+    # 2/ Send a write from MAIN
+    # 3/ Check REPLICA processed the write
+
+    BASIC_MEMGRAPH_INSTANCES_DESCRIPTION = {
+        "replica_1": {
+            "args": ["--bolt-port", "7688", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
+        },
+        "main": {
+            "args": ["--bolt-port", "7687", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                "REGISTER REPLICA replica_1 ASYNC TO '127.0.0.1:10001';",
+            ],
+        },
+    }
+
+    interactive_mg_runner.start_all(BASIC_MEMGRAPH_INSTANCES_DESCRIPTION)
+
+    cursor = connection(7688, "replica_1").cursor()
+
+    # 1/
+    # This query use to incorrectly change REPLICA storage timestamp
+    # run this multiple times to try and get into error case of MAIN timestamp < REPLICA timestamp
+    for _ in range(20):
+        execute_and_fetch_all(cursor, "SHOW INDEX INFO;")
+
+    cursor = connection(7687, "main").cursor()
+
+    # 2/
+    execute_and_fetch_all(cursor, "CREATE ();")
+
+    def retrieve_data():
+        replicas = interactive_mg_runner.MEMGRAPH_INSTANCES["main"].query("SHOW REPLICAS;")
+        return replicas
+
+    expected_data = [
+        ("replica_1", "127.0.0.1:10001", "async", 2, 0, "ready"),
+    ]
+    actual_data = mg_sleep_and_assert(expected_data, retrieve_data)
+    assert actual_data == expected_data
+
+    # 3/
+    cursor = connection(7688, "replica_1").cursor()
+    result = execute_and_fetch_all(cursor, "MATCH () RETURN count(*);")
+
+    assert len(result) == 1
+    assert result[0][0] == 1  # The one node was replicated from MAIN to REPLICA
+
+
 if __name__ == "__main__":
     sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/replication/workloads.yaml b/tests/e2e/replication/workloads.yaml
index fc239b221..c455ccc76 100644
--- a/tests/e2e/replication/workloads.yaml
+++ b/tests/e2e/replication/workloads.yaml
@@ -11,7 +11,7 @@ template_validation_queries: &template_validation_queries
 template_simple_cluster: &template_simple_cluster
   cluster:
     replica_1:
-      args: [ "--bolt-port", "7688", "--log-level=TRACE" ]
+      args: [ "--bolt-port", "7688", "--log-level=TRACE"]
       log_file: "replication-e2e-replica1.log"
       setup_queries: [ "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;" ]
     replica_2:
@@ -25,6 +25,7 @@ template_simple_cluster: &template_simple_cluster
         "REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:10001'",
         "REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:10002'",
       ]
+
 template_cluster: &template_cluster
   cluster:
     replica_1:
@@ -50,7 +51,6 @@ template_cluster: &template_cluster
         "REGISTER REPLICA replica_2 SYNC TO '127.0.0.1:10002'",
         "REGISTER REPLICA replica_3 ASYNC TO '127.0.0.1:10003'"
       ]
-      <<: *template_validation_queries
 
 workloads:
   - name: "Constraints"
diff --git a/tests/e2e/replication_experimental/CMakeLists.txt b/tests/e2e/replication_experimental/CMakeLists.txt
new file mode 100644
index 000000000..cd6e09f38
--- /dev/null
+++ b/tests/e2e/replication_experimental/CMakeLists.txt
@@ -0,0 +1,10 @@
+find_package(gflags REQUIRED)
+
+copy_e2e_python_files(replication_experiment common.py)
+copy_e2e_python_files(replication_experiment conftest.py)
+copy_e2e_python_files(replication_experiment multitenancy.py)
+copy_e2e_python_files_from_parent_folder(replication_experiment ".." memgraph.py)
+copy_e2e_python_files_from_parent_folder(replication_experiment ".." interactive_mg_runner.py)
+copy_e2e_python_files_from_parent_folder(replication_experiment ".." mg_utils.py)
+
+copy_e2e_files(replication_experiment workloads.yaml)
diff --git a/tests/e2e/replication_experimental/common.py b/tests/e2e/replication_experimental/common.py
new file mode 100644
index 000000000..dc104d628
--- /dev/null
+++ b/tests/e2e/replication_experimental/common.py
@@ -0,0 +1,25 @@
+# Copyright 2022 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import typing
+
+import mgclient
+
+
+def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
+    cursor.execute(query, params)
+    return cursor.fetchall()
+
+
+def connect(**kwargs) -> mgclient.Connection:
+    connection = mgclient.connect(**kwargs)
+    connection.autocommit = True
+    return connection
diff --git a/tests/e2e/replication_experimental/conftest.py b/tests/e2e/replication_experimental/conftest.py
new file mode 100644
index 000000000..f91333cbf
--- /dev/null
+++ b/tests/e2e/replication_experimental/conftest.py
@@ -0,0 +1,33 @@
+# Copyright 2022 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import pytest
+from common import connect, execute_and_fetch_all
+
+
+@pytest.fixture(scope="function")
+def connection():
+    connection_holder = None
+    role_holder = None
+
+    def inner_connection(port, role):
+        nonlocal connection_holder, role_holder
+        connection_holder = connect(host="localhost", port=port)
+        role_holder = role
+        return connection_holder
+
+    yield inner_connection
+
+    # Only main instance can be cleaned up because replicas do NOT accept
+    # writes.
+    if role_holder == "main":
+        cursor = connection_holder.cursor()
+        execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
diff --git a/tests/e2e/replication_experimental/multitenancy.py b/tests/e2e/replication_experimental/multitenancy.py
new file mode 100644
index 000000000..7eb699341
--- /dev/null
+++ b/tests/e2e/replication_experimental/multitenancy.py
@@ -0,0 +1,1046 @@
+# Copyright 2022 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import atexit
+import os
+import shutil
+import sys
+import tempfile
+import time
+from functools import partial
+
+import interactive_mg_runner
+import mgclient
+import pytest
+from common import execute_and_fetch_all
+from mg_utils import mg_sleep_and_assert
+
+interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+interactive_mg_runner.PROJECT_DIR = os.path.normpath(
+    os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..")
+)
+interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build"))
+interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph"))
+
+BOLT_PORTS = {"main": 7687, "replica_1": 7688, "replica_2": 7689}
+REPLICATION_PORTS = {"replica_1": 10001, "replica_2": 10002}
+
+MEMGRAPH_INSTANCES_DESCRIPTION = {
+    "replica_1": {
+        "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+        "log_file": "replica1.log",
+        "setup_queries": [f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};"],
+    },
+    "replica_2": {
+        "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
+        "log_file": "replica2.log",
+        "setup_queries": [f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};"],
+    },
+    "main": {
+        "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+        "log_file": "main.log",
+        "setup_queries": [
+            f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
+            f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';",
+        ],
+    },
+}
+
+TEMP_DIR = tempfile.TemporaryDirectory().name
+
+MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY = {
+    "replica_1": {
+        "args": [
+            "--bolt-port",
+            f"{BOLT_PORTS['replica_1']}",
+            "--log-level=TRACE",
+            "--replication-restore-state-on-startup",
+            "--data-recovery-on-startup",
+        ],
+        "log_file": "replica1.log",
+        "data_directory": TEMP_DIR + "/replica1",
+    },
+    "replica_2": {
+        "args": [
+            "--bolt-port",
+            f"{BOLT_PORTS['replica_2']}",
+            "--log-level=TRACE",
+            "--replication-restore-state-on-startup",
+            "--data-recovery-on-startup",
+        ],
+        "log_file": "replica2.log",
+        "data_directory": TEMP_DIR + "/replica2",
+    },
+    "main": {
+        "args": [
+            "--bolt-port",
+            f"{BOLT_PORTS['main']}",
+            "--log-level=TRACE",
+            "--replication-restore-state-on-startup",
+            "--data-recovery-on-startup",
+        ],
+        "log_file": "main.log",
+        "data_directory": TEMP_DIR + "/main",
+    },
+}
+
+
+def safe_execute(function, *args):
+    try:
+        function(*args)
+    except:
+        pass
+
+
+def setup_replication(connection):
+    # Setup replica1
+    cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    execute_and_fetch_all(cursor, f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};")
+    # Setup replica2
+    cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+    execute_and_fetch_all(cursor, f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};")
+    # Setup main
+    cursor = connection(BOLT_PORTS["main"], "main").cursor()
+    execute_and_fetch_all(cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';")
+    execute_and_fetch_all(cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';")
+
+
+def setup_main(main_cursor):
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)")
+
+    execute_and_fetch_all(main_cursor, "USE DATABASE B;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'B'});")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'B'});")
+
+
+def show_replicas_func(cursor, db_name):
+    def func():
+        execute_and_fetch_all(cursor, f"USE DATABASE {db_name};")
+        return set(execute_and_fetch_all(cursor, "SHOW REPLICAS;"))
+
+    return func
+
+
+def show_databases_func(cursor):
+    def func():
+        return execute_and_fetch_all(cursor, "SHOW DATABASES;")
+
+    return func
+
+
+def get_number_of_nodes_func(cursor, db_name):
+    def func():
+        execute_and_fetch_all(cursor, f"USE DATABASE {db_name};")
+        return execute_and_fetch_all(cursor, "MATCH (n) RETURN count(*);")[0][0]
+
+    return func
+
+
+def get_number_of_edges_func(cursor, db_name):
+    def func():
+        execute_and_fetch_all(cursor, f"USE DATABASE {db_name};")
+        return execute_and_fetch_all(cursor, "MATCH ()-[r]->() RETURN count(*);")[0][0]
+
+    return func
+
+
+def test_manual_databases_create_multitenancy_replication(connection):
+    # Goal: to show that replication can be established against REPLICA which already
+    # has the clean databases we need
+    # 0/ MAIN CREATE DATABASE A + B
+    #    REPLICA CREATE DATABASE A + B
+    #    Setup replication
+    # 1/ Write to MAIN A, Write to MAIN B
+    # 2/ Validate replication of changes to A + B have arrived at REPLICA
+
+    MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
+        "replica_1": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "CREATE DATABASE B;",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
+            ],
+        },
+        "replica_2": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
+            "log_file": "replica2.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "CREATE DATABASE B;",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};",
+            ],
+        },
+        "main": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "CREATE DATABASE B;",
+                f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
+                f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';",
+            ],
+        },
+    }
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL)
+    cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(cursor, "USE DATABASE A;")
+    execute_and_fetch_all(cursor, "CREATE ();")
+    execute_and_fetch_all(cursor, "USE DATABASE B;")
+    execute_and_fetch_all(cursor, "CREATE ()-[:EDGE]->();")
+
+    # 2/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "B"))
+
+    cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 1
+    assert get_number_of_edges_func(cursor_replica, "A")() == 0
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 2
+    assert get_number_of_edges_func(cursor_replica, "B")() == 1
+
+    cursor_replica2 = connection(BOLT_PORTS["replica_1"], "replica_2").cursor()
+    assert get_number_of_nodes_func(cursor_replica2, "A")() == 1
+    assert get_number_of_edges_func(cursor_replica2, "A")() == 0
+    assert get_number_of_nodes_func(cursor_replica2, "B")() == 2
+    assert get_number_of_edges_func(cursor_replica2, "B")() == 1
+
+
+def test_manual_databases_create_multitenancy_replication_branching(connection):
+    # Goal: to show that replication can be established against REPLICA which already
+    # has all the databases and the same data
+    # 0/ MAIN CREATE DATABASE A + B and fill with data
+    #    REPLICA CREATE DATABASE A + B and fil with exact data
+    #    Setup REPLICA
+    # 1/ Registering REPLICA on MAIN should not fail due to tenant branching
+
+    MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
+        "replica_1": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE ()",
+                "CREATE DATABASE B;",
+                "USE DATABASE B;",
+                "CREATE ()-[:EDGE]->()",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
+            ],
+        },
+        "replica_2": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
+            "log_file": "replica2.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE ()",
+                "CREATE DATABASE B;",
+                "USE DATABASE B;",
+                "CREATE ()-[:EDGE]->()",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};",
+            ],
+        },
+        "main": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE ()",
+                "CREATE DATABASE B;",
+                "USE DATABASE B;",
+                "CREATE ()-[:EDGE]->()",
+            ],
+        },
+    }
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL)
+    cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    failed = False
+    try:
+        execute_and_fetch_all(
+            cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';"
+        )
+    except mgclient.DatabaseError:
+        failed = True
+    assert not failed
+
+    try:
+        execute_and_fetch_all(
+            cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';"
+        )
+    except mgclient.DatabaseError:
+        failed = True
+    assert not failed
+
+
+def test_manual_databases_create_multitenancy_replication_dirty_replica(connection):
+    # Goal: to show that replication can be established against REPLICA which already
+    # has all the databases we need, even when they branched
+    # 0/ MAIN CREATE DATABASE A
+    #    REPLICA CREATE DATABASE A
+    #    REPLICA write to A
+    #    Setup REPLICA
+    # 1/ Register replica; should fail
+
+    MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
+        "replica_1": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE (:Node{from:'A'})",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
+            ],
+        },
+        "replica_2": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
+            "log_file": "replica2.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE (:Node{from:'A'})",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};",
+            ],
+        },
+        "main": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+            ],
+        },
+    }
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL)
+    cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    failed = False
+    try:
+        execute_and_fetch_all(
+            cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';"
+        )
+    except mgclient.DatabaseError:
+        failed = True
+    assert not failed
+
+    try:
+        execute_and_fetch_all(
+            cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';"
+        )
+    except mgclient.DatabaseError:
+        failed = True
+    assert not failed
+
+
+def test_manual_databases_create_multitenancy_replication_main_behind(connection):
+    # Goal: to show that replication can be established against REPLICA which has
+    # different branched databases
+    # 0/ REPLICA CREATE DATABASE A
+    #    REPLICA write to A
+    #    Setup replication
+    # 1/ MAIN CREATE DATABASE A
+    # 2/ Check that database has been replicated
+
+    MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
+        "replica_1": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE (:Node{from:'A'})",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
+            ],
+        },
+        "replica_2": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
+            "log_file": "replica2.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE (:Node{from:'A'})",
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};",
+            ],
+        },
+        "main": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
+                f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';",
+            ],
+        },
+    }
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+
+    # 2/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    databases_on_main = show_databases_func(main_cursor)()
+
+    replica_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor))
+
+    replica_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+    mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor))
+
+
+def test_automatic_databases_create_multitenancy_replication(connection):
+    # Goal: to show that replication can be established against REPLICA where a new databases
+    # needs replication
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A
+    # 2/ Write to MAIN A
+    # 3/ Validate replication of changes to A have arrived at REPLICA
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)")
+
+    # 3/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B"))
+
+    cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 7
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 0
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+    cursor_replica = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 7
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 0
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+
+def test_automatic_databases_multitenancy_replication_predefined(connection):
+    # Goal: to show that replication can be established against REPLICA which doesn't
+    # have any additional databases; MAIN's database clean at registration time
+    # 0/ MAIN CREATE DATABASE A + B
+    #    Setup replication
+    # 1/ Write to MAIN A, Write to MAIN B
+    # 2/ Validate replication of changes to A + B have arrived at REPLICA
+
+    MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
+        "replica_1": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": [
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
+            ],
+        },
+        "main": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "CREATE DATABASE B;",
+                f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
+            ],
+        },
+    }
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL)
+    cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(cursor, "USE DATABASE A;")
+    execute_and_fetch_all(cursor, "CREATE ();")
+    execute_and_fetch_all(cursor, "USE DATABASE B;")
+    execute_and_fetch_all(cursor, "CREATE ()-[:EDGE]->();")
+
+    # 2/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "B"))
+
+    cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 1
+    assert get_number_of_edges_func(cursor_replica, "A")() == 0
+
+
+def test_automatic_databases_create_multitenancy_replication_dirty_main(connection):
+    # Goal: to show that replication can be established against REPLICA which doesn't
+    # have any additional databases; MAIN's database dirty at registration time
+    # 0/ MAIN CREATE DATABASE A
+    #    MAIN write to A
+    #    Setup replication
+    # 1/ Validate
+
+    MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
+        "replica_1": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
+            "log_file": "replica1.log",
+            "setup_queries": [
+                f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
+            ],
+        },
+        "main": {
+            "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
+            "log_file": "main.log",
+            "setup_queries": [
+                "CREATE DATABASE A;",
+                "USE DATABASE A;",
+                "CREATE (:Node{from:'A'})",
+                f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
+            ],
+        },
+    }
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL)
+    cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "A"))
+
+    cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    execute_and_fetch_all(cursor_replica, "USE DATABASE A;")
+    actual_data = execute_and_fetch_all(cursor_replica, "MATCH (n) RETURN count(*);")
+    assert actual_data[0][0] == 1  # one node
+    actual_data = execute_and_fetch_all(cursor_replica, "MATCH ()-[r]->() RETURN count(*);")
+    assert actual_data[0][0] == 0  # zero relationships
+
+
+@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")])
+def test_multitenancy_replication_restart_replica_w_fc(connection, replica_name):
+    # Goal: show that a replica can be recovered with the frequent checker
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A and B
+    # 2/ Write on MAIN to A and B
+    # 3/ Restart replica
+    # 4/ Validate data on replica
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    setup_main(main_cursor)
+
+    # 3/
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name)
+    time.sleep(3)  # In order for the frequent check to run
+    # Check that the FC did invalidate
+    expected_data = {
+        "replica_1": {
+            ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "invalid"),
+            ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"),
+        },
+        "replica_2": {
+            ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"),
+            ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "invalid"),
+        },
+    }
+    assert expected_data[replica_name] == show_replicas_func(main_cursor, "A")()
+    # Restart
+    interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name)
+
+    # 4/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 3, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 3, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B"))
+
+    cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor()
+
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 7
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 2
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+
+@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")])
+def test_multitenancy_replication_restart_replica_wo_fc(connection, replica_name):
+    # Goal: show that a replica can be recovered without the frequent checker detecting it being down
+    # needs replicating over
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A and B
+    # 2/ Write on MAIN to A and B
+    # 3/ Restart replica
+    # 4/ Validate data on replica
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    setup_main(main_cursor)
+
+    # 3/
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name)
+    interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name)
+
+    # 4/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 3, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 3, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B"))
+
+    cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor()
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 7
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 2
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+
+@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")])
+def test_multitenancy_replication_restart_replica_w_fc_w_rec(connection, replica_name):
+    # Goal: show that a replica recovers data on reconnect
+    # needs replicating over
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A and B
+    # 2/ Write on MAIN to A and B
+    # 3/ Restart replica
+    # 4/ Validate data on replica
+
+    # 0/
+    # Tmp dir should already be removed, but sometimes its not...
+    safe_execute(shutil.rmtree, TEMP_DIR)
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY)
+    setup_replication(connection)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    setup_main(main_cursor)
+
+    # 3/
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, replica_name)
+    safe_execute(execute_and_fetch_all, main_cursor, "USE DATABASE A;")
+    safe_execute(execute_and_fetch_all, main_cursor, "CREATE (:Node{on:'A'});")
+    safe_execute(execute_and_fetch_all, main_cursor, "USE DATABASE B;")
+    safe_execute(execute_and_fetch_all, main_cursor, "CREATE (:Node{on:'B'});")
+    interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, replica_name)
+
+    # 4/
+    cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor()
+
+    mg_sleep_and_assert(8, get_number_of_nodes_func(cursor_replica, "A"))
+    mg_sleep_and_assert(3, get_number_of_edges_func(cursor_replica, "A"))
+
+    mg_sleep_and_assert(3, get_number_of_nodes_func(cursor_replica, "B"))
+    mg_sleep_and_assert(0, get_number_of_edges_func(cursor_replica, "B"))
+
+
+@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")])
+def test_multitenancy_replication_drop_replica(connection, replica_name):
+    # Goal: show that the cluster can recover if a replica is dropped and registered again
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A and B
+    # 2/ Write on MAIN to A and B
+    # 3/ Drop and add the same replica
+    # 4/ Validate data on replica
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    setup_main(main_cursor)
+
+    # 3/
+    execute_and_fetch_all(main_cursor, f"DROP REPLICA {replica_name};")
+    sync = {"replica_1": "SYNC", "replica_2": "ASYNC"}
+    execute_and_fetch_all(
+        main_cursor,
+        f"REGISTER REPLICA  {replica_name} {sync[replica_name]} TO '127.0.0.1:{REPLICATION_PORTS[replica_name]}';",
+    )
+
+    # 4/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 3, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 3, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B"))
+
+    cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor()
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 7
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 2
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+
+def test_multitenancy_replication_restart_main(connection):
+    # Goal: show that the cluster can restore to a correct state if the MAIN restarts
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A and B
+    # 2/ Write on MAIN to A and B
+    # 3/ Restart main and write new data
+    # 4/ Validate data on replica
+
+    # 0/
+    # Tmp dir should already be removed, but sometimes its not...
+    safe_execute(shutil.rmtree, TEMP_DIR)
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY)
+    setup_replication(connection)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    setup_main(main_cursor)
+
+    # 3/
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, "main")
+    interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, "main")
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});")
+    execute_and_fetch_all(main_cursor, "USE DATABASE B;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'B'});")
+
+    # 4/
+    cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    execute_and_fetch_all(cursor_replica, "USE DATABASE A;")
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 8
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 3
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+    cursor_replica = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+    execute_and_fetch_all(cursor_replica, "USE DATABASE A;")
+    assert get_number_of_nodes_func(cursor_replica, "A")() == 8
+    assert get_number_of_edges_func(cursor_replica, "A")() == 3
+    assert get_number_of_nodes_func(cursor_replica, "B")() == 3
+    assert get_number_of_edges_func(cursor_replica, "B")() == 0
+
+
+def test_automatic_databases_drop_multitenancy_replication(connection):
+    # Goal: show that drop database can be replicated
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A
+    # 2/ Write to MAIN A
+    # 3/ Validate replication of changes to A have arrived at REPLICA
+    # 4/ DROP DATABASE A/B
+    # 5/ Check that the drop replicated
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});")
+
+    # 3/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B"))
+
+    # 4/
+    execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;")
+    execute_and_fetch_all(main_cursor, "DROP DATABASE A;")
+    execute_and_fetch_all(main_cursor, "DROP DATABASE B;")
+
+    # 5/
+    databases_on_main = show_databases_func(main_cursor)()
+
+    replica_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor))
+
+    replica_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+    mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor))
+
+
+@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")])
+def test_drop_multitenancy_replication_restart_replica(connection, replica_name):
+    # Goal: show that the drop database can be restored
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A and B
+    # 2/ Write on MAIN to A and B
+    # 3/ Restart SYNC replica and drop database
+    # 4/ Validate data on replica
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+
+    # 2/
+    setup_main(main_cursor)
+
+    # 3/
+    interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name)
+    execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;")
+    execute_and_fetch_all(main_cursor, "DROP DATABASE B;")
+    interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name)
+
+    # 4/
+    databases_on_main = show_databases_func(main_cursor)()
+
+    replica_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor))
+
+    replica_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+    mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor))
+
+
+def test_multitenancy_drop_while_replica_using(connection):
+    # Goal: show that the replica can handle a transaction on a database being dropped (will persist until tx finishes)
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A
+    # 2/ Write to MAIN A
+    # 3/ Validate replication of changes to A have arrived at REPLICA
+    # 4/ Start A transaction on replica 1, Use A on replica2
+    # 5/ Check that the drop replicated
+    # 6/ Validate that the transaction is still active and working and that the replica2 is not pointing to anything
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+
+    # 2/
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});")
+
+    # 3/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    # 4/
+    replica1_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    replica2_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+
+    execute_and_fetch_all(replica1_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(replica1_cursor, "BEGIN")
+    execute_and_fetch_all(replica2_cursor, "USE DATABASE A;")
+
+    execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;")
+    execute_and_fetch_all(main_cursor, "DROP DATABASE A;")
+
+    # 5/
+    # TODO Remove this once there is a replica state for the system
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
+    execute_and_fetch_all(main_cursor, "USE DATABASE B;")
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B"))
+
+    # 6/
+    assert execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN count(*);")[0][0] == 1
+    execute_and_fetch_all(replica1_cursor, "COMMIT")
+    failed = False
+    try:
+        execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN n;")
+    except mgclient.DatabaseError:
+        failed = True
+    assert failed
+
+    failed = False
+    try:
+        execute_and_fetch_all(replica2_cursor, "MATCH(n) RETURN n;")
+    except mgclient.DatabaseError:
+        failed = True
+    assert failed
+
+
+def test_multitenancy_drop_and_recreate_while_replica_using(connection):
+    # Goal: show that the replica can handle a transaction on a database being dropped and the same name reused
+    # Original storage should persist in a nameless state until tx is over
+    # needs replicating over
+    # 0/ Setup replication
+    # 1/ MAIN CREATE DATABASE A
+    # 2/ Write to MAIN A
+    # 3/ Validate replication of changes to A have arrived at REPLICA
+    # 4/ Start A transaction on replica 1, Use A on replica2
+    # 5/ Check that the drop/create replicated
+    # 6/ Validate that the transaction is still active and working and that the replica2 is not pointing to anything
+
+    # 0/
+    interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
+    main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
+
+    # 1/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+
+    # 2/
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});")
+
+    # 3/
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    # 4/
+    replica1_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor()
+    replica2_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor()
+
+    execute_and_fetch_all(replica1_cursor, "USE DATABASE A;")
+    execute_and_fetch_all(replica1_cursor, "BEGIN")
+    execute_and_fetch_all(replica2_cursor, "USE DATABASE A;")
+
+    execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;")
+    execute_and_fetch_all(main_cursor, "DROP DATABASE A;")
+
+    # 5/
+    execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
+    execute_and_fetch_all(main_cursor, "USE DATABASE A;")
+
+    expected_data = {
+        ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"),
+        ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"),
+    }
+    mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A"))
+
+    # 6/
+    assert execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN count(*);")[0][0] == 1
+    execute_and_fetch_all(replica1_cursor, "COMMIT")
+    failed = False
+    try:
+        execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN n;")
+    except mgclient.DatabaseError:
+        failed = True
+    assert failed
+
+    failed = False
+    try:
+        execute_and_fetch_all(replica2_cursor, "MATCH(n) RETURN n;")
+    except mgclient.DatabaseError:
+        failed = True
+    assert failed
+
+
+if __name__ == "__main__":
+    interactive_mg_runner.cleanup_directories_on_exit()
+    sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/replication_experimental/workloads.yaml b/tests/e2e/replication_experimental/workloads.yaml
new file mode 100644
index 000000000..e48515f4f
--- /dev/null
+++ b/tests/e2e/replication_experimental/workloads.yaml
@@ -0,0 +1,4 @@
+workloads:
+  - name: "Replicate multitenancy"
+    binary: "tests/e2e/pytest_runner.sh"
+    args: ["replication_experimental/multitenancy.py"]
diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh
index 1aba6a517..88b70ae32 100755
--- a/tests/e2e/run.sh
+++ b/tests/e2e/run.sh
@@ -25,7 +25,7 @@ if [ "$#" -eq 0 ]; then
   # NOTE: If you want to run all tests under specific folder/section just
   # replace the dot (root directory below) with the folder name, e.g.
   # `--workloads-root-directory replication`.
-  python3 runner.py --workloads-root-directory .
+  python3 runner.py --workloads-root-directory "$SCRIPT_DIR/../../build"
 elif [ "$#" -eq 1 ]; then
   if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
     print_help
@@ -34,7 +34,7 @@ elif [ "$#" -eq 1 ]; then
   # NOTE: --workload-name comes from each individual folder/section
   # workloads.yaml file. E.g. `streams/workloads.yaml` has a list of
   # `workloads:` and each workload has it's `-name`.
-  python3 runner.py --workloads-root-directory . --workload-name "$1"
+  python3 runner.py --workloads-root-directory "$SCRIPT_DIR/../../build" --workload-name "$1"
 else
   print_help
 fi
diff --git a/tests/e2e/runner.py b/tests/e2e/runner.py
index 949670d43..ae022d4d8 100755
--- a/tests/e2e/runner.py
+++ b/tests/e2e/runner.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
 # Copyright 2021 Memgraph Ltd.
 #
 # Use of this software is governed by the Business Source License
diff --git a/tests/e2e/server/CMakeLists.txt b/tests/e2e/server/CMakeLists.txt
index a408f4a2e..2e62f2035 100644
--- a/tests/e2e/server/CMakeLists.txt
+++ b/tests/e2e/server/CMakeLists.txt
@@ -6,3 +6,5 @@ target_link_libraries(memgraph__e2e__server_connection mgclient mg-utils gflags)
 
 add_executable(memgraph__e2e__server_ssl_connection server_ssl_connection.cpp)
 target_link_libraries(memgraph__e2e__server_ssl_connection mgclient mg-utils gflags)
+
+copy_e2e_files(server workloads.yaml)
diff --git a/tests/e2e/set_properties/CMakeLists.txt b/tests/e2e/set_properties/CMakeLists.txt
index 10cc03584..66a8039b7 100644
--- a/tests/e2e/set_properties/CMakeLists.txt
+++ b/tests/e2e/set_properties/CMakeLists.txt
@@ -6,3 +6,5 @@ copy_set_properties_e2e_python_files(common.py)
 copy_set_properties_e2e_python_files(set_properties.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(set_properties workloads.yaml)
diff --git a/tests/e2e/show_index_info/CMakeLists.txt b/tests/e2e/show_index_info/CMakeLists.txt
index dd9bd28bb..b5d154355 100644
--- a/tests/e2e/show_index_info/CMakeLists.txt
+++ b/tests/e2e/show_index_info/CMakeLists.txt
@@ -4,3 +4,5 @@ endfunction()
 
 copy_show_index_info_e2e_python_files(common.py)
 copy_show_index_info_e2e_python_files(test_show_index_info.py)
+
+copy_e2e_files(show_index_info workloads.yaml)
diff --git a/tests/e2e/streams/CMakeLists.txt b/tests/e2e/streams/CMakeLists.txt
index 3c0ffac98..cbca225f7 100644
--- a/tests/e2e/streams/CMakeLists.txt
+++ b/tests/e2e/streams/CMakeLists.txt
@@ -11,3 +11,5 @@ copy_streams_e2e_python_files(pulsar_streams_tests.py)
 add_subdirectory(transformations)
 
 copy_e2e_python_files_from_parent_folder(streams ".." mg_utils.py)
+
+copy_e2e_files(streams workloads.yaml)
diff --git a/tests/e2e/streams/common.py b/tests/e2e/streams/common.py
index 43b10ba1c..66a045970 100644
--- a/tests/e2e/streams/common.py
+++ b/tests/e2e/streams/common.py
@@ -9,12 +9,12 @@
 # by the Apache License, Version 2.0, included in the file
 # licenses/APL.txt.
 
+import time
+from multiprocessing import Manager, Process, Value
+
 import mgclient
 import pytest
-import time
-
 from mg_utils import mg_sleep_and_assert
-from multiprocessing import Manager, Process, Value
 
 # These are the indices of the different values in the result of SHOW STREAM
 # query
@@ -103,15 +103,53 @@ def get_stream_info(cursor, stream_name):
 
 def get_is_running(cursor, stream_name):
     stream_info = get_stream_info(cursor, stream_name)
-
-    assert stream_info
+    assert stream_info is not None
     return stream_info[IS_RUNNING]
 
 
-def start_stream(cursor, stream_name):
-    execute_and_fetch_all(cursor, f"START STREAM {stream_name}")
+def create_stream(
+    cursor,
+    stream_name,
+    topics,
+    transformation,
+    consumer_group=None,
+    batch_interval=None,
+    batch_size=None,
+    bootstrap_servers=None,
+    configs=None,
+    credentials=None,
+):
+    query_str = f"CREATE KAFKA STREAM {stream_name} TOPICS {topics} TRANSFORM {transformation}"
+    if consumer_group is not None:
+        query_str += f" CONSUMER_GROUP {consumer_group}"
+    if batch_interval is not None:
+        query_str += f" BATCH_INTERVAL {batch_interval}"
+    if batch_size is not None:
+        query_str += f" BATCH_SIZE {batch_size}"
+    if bootstrap_servers is not None:
+        query_str += f" BOOTSTRAP_SERVERS {bootstrap_servers}"
+    if configs is not None:
+        query_str += f" CONFIGS {configs}"
+    if credentials is not None:
+        query_str += f" CREDENTIALS {credentials}"
+    execute_and_fetch_all(cursor, query_str)
 
+
+def start_stream(cursor, stream_name, sleep=True):
+    # Sleep is needed because although is_running returns True,
+    # the stream cannot accept messages yet
+    execute_and_fetch_all(cursor, f"START STREAM {stream_name}")
     assert get_is_running(cursor, stream_name)
+    if sleep:
+        time.sleep(5)
+
+
+def start_streams(cursor, stream_names):
+    # Start every stream but don't sleep after each creation
+    for stream_name in stream_names:
+        execute_and_fetch_all(cursor, f"START STREAM {stream_name}")
+        assert get_is_running(cursor, stream_name)
+    time.sleep(5)
 
 
 def start_stream_with_limit(cursor, stream_name, batch_limit, timeout=None):
@@ -123,13 +161,11 @@ def start_stream_with_limit(cursor, stream_name, batch_limit, timeout=None):
 
 def stop_stream(cursor, stream_name):
     execute_and_fetch_all(cursor, f"STOP STREAM {stream_name}")
-
     assert not get_is_running(cursor, stream_name)
 
 
 def drop_stream(cursor, stream_name):
     execute_and_fetch_all(cursor, f"DROP STREAM {stream_name}")
-
     assert get_stream_info(cursor, stream_name) is None
 
 
@@ -227,7 +263,7 @@ def test_start_and_stop_during_check(
     try:
         check_stream_proc.start()
 
-        time.sleep(0.5)
+        time.sleep(3)
 
         assert timed_wait(lambda: check_counter.value == CHECK_BEFORE_EXECUTE)
         assert timed_wait(lambda: get_is_running(cursor, "test_stream"))
@@ -260,25 +296,22 @@ def test_start_and_stop_during_check(
 
 def test_start_checked_stream_after_timeout(connection, stream_creator):
     cursor = connection.cursor()
-    execute_and_fetch_all(cursor, stream_creator("test_stream"))
+    stream_name = "test_start_checked_stream_after_timeout"
+    execute_and_fetch_all(cursor, stream_creator(stream_name))
 
-    TIMEOUT_IN_MS = 2000
-    TIMEOUT_IN_SECONDS = TIMEOUT_IN_MS / 1000
+    timeout_in_ms = 2000
 
     def call_check():
-        execute_and_fetch_all(connect().cursor(), f"CHECK STREAM test_stream TIMEOUT {TIMEOUT_IN_MS}")
+        execute_and_fetch_all(connect().cursor(), f"CHECK STREAM {stream_name} TIMEOUT {timeout_in_ms}")
 
     check_stream_proc = Process(target=call_check, daemon=True)
 
-    start = time.time()
     check_stream_proc.start()
-    assert timed_wait(lambda: get_is_running(cursor, "test_stream"))
-    start_stream(cursor, "test_stream")
-    end = time.time()
+    assert timed_wait(lambda: get_is_running(cursor, stream_name))
+    start_stream(cursor, stream_name)
 
-    assert (end - start) < 1.3 * TIMEOUT_IN_SECONDS, "The START STREAM was blocked too long"
-    assert get_is_running(cursor, "test_stream")
-    stop_stream(cursor, "test_stream")
+    assert get_is_running(cursor, stream_name)
+    stop_stream(cursor, stream_name)
 
 
 def test_check_stream_same_number_of_queries_than_messages(connection, stream_creator, message_sender):
@@ -313,7 +346,6 @@ def test_check_stream_same_number_of_queries_than_messages(connection, stream_cr
     # #            {parameters: {"value": "Parameter: 04"}, query: "Message: 04"}]
     # # -Batch 3: [{parameters: {"value": "Parameter: 05"}, query: "Message: 05"},
     # #            {parameters: {"value": "Parameter: 06"}, query: "Message: 06"}]
-
     assert len(test_results.value) == BATCH_LIMIT
 
     expected_queries_and_raw_messages_1 = (
@@ -351,7 +383,7 @@ def test_check_stream_different_number_of_queries_than_messages(connection, stre
     STREAM_NAME = "test_stream"
     cursor = connection.cursor()
     execute_and_fetch_all(cursor, stream_creator(STREAM_NAME, BATCH_SIZE))
-    time.sleep(2)
+    time.sleep(3)
 
     results = Manager().Namespace()
 
@@ -413,30 +445,32 @@ def test_check_stream_different_number_of_queries_than_messages(connection, stre
     assert expected_queries_and_raw_messages_3 == results.value[2]
 
 
-def test_start_stream_with_batch_limit(connection, stream_creator, messages_sender):
-    STREAM_NAME = "test"
+def test_start_stream_with_batch_limit(connection, stream_name, stream_creator, messages_sender):
     BATCH_LIMIT = 5
+    TIMEOUT = 10000
 
     cursor = connection.cursor()
-    execute_and_fetch_all(cursor, stream_creator(STREAM_NAME))
+    execute_and_fetch_all(cursor, stream_creator())
+    results = execute_and_fetch_all(connection.cursor(), "SHOW STREAMS")
+    assert len(results) == 1
 
-    def start_new_stream_with_limit(stream_name, batch_limit):
+    def start_new_stream_with_limit():
         connection = connect()
         cursor = connection.cursor()
-        start_stream_with_limit(cursor, stream_name, batch_limit)
-
-    thread_stream_running = Process(target=start_new_stream_with_limit, daemon=True, args=(STREAM_NAME, BATCH_LIMIT))
-    thread_stream_running.start()
+        start_stream_with_limit(cursor, stream_name, BATCH_LIMIT, TIMEOUT)
 
     def is_running():
-        return get_is_running(cursor, STREAM_NAME)
+        return get_is_running(cursor, stream_name)
 
+    thread_stream_running = Process(target=start_new_stream_with_limit)
+    thread_stream_running.start()
+
+    execute_and_fetch_all(connection.cursor(), "SHOW STREAMS")
     assert mg_sleep_and_assert(True, is_running)
-
     messages_sender(BATCH_LIMIT - 1)
 
     # We have not sent enough batches to reach the limit. We check that the stream is still correctly running.
-    assert get_is_running(cursor, STREAM_NAME)
+    assert get_is_running(cursor, stream_name)
 
     # We send a last message to reach the batch_limit
     messages_sender(1)
@@ -549,7 +583,6 @@ def test_check_while_stream_with_batch_limit_running(connection, stream_creator,
     STREAM_NAME = "test_batch_limit_and_check"
     BATCH_LIMIT = 1
     TIMEOUT = 10000
-    TIMEOUT_IN_SECONDS = TIMEOUT / 1000
 
     cursor = connection.cursor()
     execute_and_fetch_all(cursor, stream_creator(STREAM_NAME))
@@ -625,7 +658,7 @@ def test_check_stream_with_batch_limit_with_invalid_batch_limit(connection, stre
 
     cursor = connection.cursor()
     execute_and_fetch_all(cursor, stream_creator(STREAM_NAME))
-    time.sleep(2)
+    time.sleep(3)
 
     # 1/ checking with batch_limit=-10
     batch_limit = -10
diff --git a/tests/e2e/streams/conftest.py b/tests/e2e/streams/conftest.py
index a26138b53..1bf3544c2 100644
--- a/tests/e2e/streams/conftest.py
+++ b/tests/e2e/streams/conftest.py
@@ -9,15 +9,14 @@
 # by the Apache License, Version 2.0, included in the file
 # licenses/APL.txt.
 
+import pulsar
 import pytest
+from common import NAME, PULSAR_SERVICE_URL, connect, execute_and_fetch_all
 from kafka import KafkaProducer
 from kafka.admin import KafkaAdminClient, NewTopic
 
-import pulsar
 import requests
 
-from common import NAME, connect, execute_and_fetch_all, PULSAR_SERVICE_URL
-
 # To run these test locally a running Kafka sever is necessery. The test tries
 # to connect on localhost:9092.
 
@@ -37,29 +36,22 @@ def connection():
 
 
 def get_topics(num):
-    return [f'topic_{i}' for i in range(num)]
+    return [f"topic_{i}" for i in range(num)]
 
 
 @pytest.fixture(scope="function")
 def kafka_topics():
-    admin_client = KafkaAdminClient(
-        bootstrap_servers="localhost:9092",
-        client_id="test")
+    admin_client = KafkaAdminClient(bootstrap_servers="localhost:29092", client_id="test")
     # The issue arises if we remove default kafka topics, e.g.
     # "__consumer_offsets"
-    previous_topics = [
-        topic for topic in admin_client.list_topics() if topic != "__consumer_offsets"]
+    previous_topics = [topic for topic in admin_client.list_topics() if topic != "__consumer_offsets"]
     if previous_topics:
         admin_client.delete_topics(topics=previous_topics, timeout_ms=5000)
 
     topics = get_topics(3)
     topics_to_create = []
     for topic in topics:
-        topics_to_create.append(
-            NewTopic(
-                name=topic,
-                num_partitions=1,
-                replication_factor=1))
+        topics_to_create.append(NewTopic(name=topic, num_partitions=1, replication_factor=1))
 
     admin_client.create_topics(new_topics=topics_to_create, timeout_ms=5000)
     yield topics
@@ -68,7 +60,7 @@ def kafka_topics():
 
 @pytest.fixture(scope="function")
 def kafka_producer():
-    yield KafkaProducer(bootstrap_servers="localhost:9092")
+    yield KafkaProducer(bootstrap_servers=["localhost:29092"], api_version_auto_timeout_ms=10000)
 
 
 @pytest.fixture(scope="function")
@@ -80,6 +72,5 @@ def pulsar_client():
 def pulsar_topics():
     topics = get_topics(3)
     for topic in topics:
-        requests.delete(
-            f'http://127.0.0.1:6652/admin/v2/persistent/public/default/{topic}?force=true')
+        requests.delete(f"http://localhost:6652/admin/v2/persistent/public/default/{topic}?force=true")
     yield topics
diff --git a/tests/e2e/streams/kafka.yml b/tests/e2e/streams/kafka.yml
deleted file mode 100644
index f9d213864..000000000
--- a/tests/e2e/streams/kafka.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-version: '3.7'
-services:
-  zookeeper:
-    image: 'bitnami/zookeeper:latest'
-    ports:
-      - '2181:2181'
-    environment:
-      - ALLOW_ANONYMOUS_LOGIN=yes
-  kafka:
-    image: 'bitnami/kafka:latest'
-    ports:
-      - '9092:9092'
-    environment:
-      - KAFKA_BROKER_ID=1
-      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
-      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092
-      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
-      - ALLOW_PLAINTEXT_LISTENER=yes
-    depends_on:
-      - zookeeper
diff --git a/tests/e2e/streams/kafka/docker-compose.yml b/tests/e2e/streams/kafka/docker-compose.yml
new file mode 100755
index 000000000..3ab2166af
--- /dev/null
+++ b/tests/e2e/streams/kafka/docker-compose.yml
@@ -0,0 +1,22 @@
+version: '3'
+services:
+  zookeeper:
+    image: 'bitnami/zookeeper:3.9.1'
+    ports:
+      - '2181:2181'
+    environment:
+      - ALLOW_ANONYMOUS_LOGIN=yes
+  kafka:
+    image: 'bitnami/kafka:3.6.1'
+    ports:
+      - '9092:9092'
+      - '29092:29092'
+    environment:
+      - KAFKA_BROKER_ID=1
+      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
+      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,PLAINTEXT_HOST://:29092
+      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092,PLAINTEXT_HOST://localhost:29092
+      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
+      - ALLOW_PLAINTEXT_LISTENER=yes
+    depends_on:
+      - zookeeper
diff --git a/tests/e2e/streams/kafka_streams_tests.py b/tests/e2e/streams/kafka_streams_tests.py
index 3c1213019..b988a6c26 100755
--- a/tests/e2e/streams/kafka_streams_tests.py
+++ b/tests/e2e/streams/kafka_streams_tests.py
@@ -12,31 +12,30 @@
 # licenses/APL.txt.
 
 import sys
-import pytest
-import mgclient
 import time
-from mg_utils import mg_sleep_and_assert
-from multiprocessing import Process, Value
+from multiprocessing import Process
+
 import common
+import mgclient
+import pytest
+from mg_utils import mg_sleep_and_assert
 
 TRANSFORMATIONS_TO_CHECK_C = ["c_transformations.empty_transformation"]
-
 TRANSFORMATIONS_TO_CHECK_PY = ["kafka_transform.simple", "kafka_transform.with_parameters"]
+KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT = 60
 
 
 @pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
 def test_simple(kafka_producer, kafka_topics, connection, transformation):
     assert len(kafka_topics) > 0
+    stream_name = "test_simple_" + transformation.split(".")[1]
+
     cursor = connection.cursor()
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM test TOPICS {','.join(kafka_topics)} TRANSFORM {transformation}",
-    )
-    common.start_stream(cursor, "test")
-    time.sleep(5)
+    common.create_stream(cursor, stream_name, ",".join(kafka_topics), transformation)
+    common.start_stream(cursor, stream_name)
 
     for topic in kafka_topics:
-        kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=60)
+        kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     for topic in kafka_topics:
         common.kafka_check_vertex_exists_with_topic_and_payload(cursor, topic, common.SIMPLE_MSG)
@@ -47,22 +46,15 @@ def test_separate_consumers(kafka_producer, kafka_topics, connection, transforma
     assert len(kafka_topics) > 0
     cursor = connection.cursor()
 
-    stream_names = []
-    for topic in kafka_topics:
-        stream_name = "stream_" + topic
-        stream_names.append(stream_name)
-        common.execute_and_fetch_all(
-            cursor,
-            f"CREATE KAFKA STREAM {stream_name} TOPICS {topic} TRANSFORM {transformation}",
-        )
+    stream_names = ["stream_" + transformation.split(".")[1] + "_" + topic for topic in kafka_topics]
 
-    for stream_name in stream_names:
-        common.start_stream(cursor, stream_name)
+    for stream_name, topic in zip(stream_names, kafka_topics):
+        common.create_stream(cursor, stream_name, topic, transformation)
 
-    time.sleep(5)
+    common.start_streams(cursor, stream_names)
 
     for topic in kafka_topics:
-        kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=60)
+        kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     for topic in kafka_topics:
         common.kafka_check_vertex_exists_with_topic_and_payload(cursor, topic, common.SIMPLE_MSG)
@@ -77,23 +69,20 @@ def test_start_from_last_committed_offset(kafka_producer, kafka_topics, connecti
     # restarting Memgraph during a single workload cannot be done currently.
     assert len(kafka_topics) > 0
     cursor = connection.cursor()
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM test TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple",
-    )
-    common.start_stream(cursor, "test")
-    time.sleep(1)
 
-    kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60)
+    stream_name = "test_start_from_last_committed_offset"
+    common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.simple")
+    common.start_stream(cursor, stream_name)
 
+    kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
     common.kafka_check_vertex_exists_with_topic_and_payload(cursor, kafka_topics[0], common.SIMPLE_MSG)
 
-    common.stop_stream(cursor, "test")
-    common.drop_stream(cursor, "test")
+    common.stop_stream(cursor, stream_name)
+    common.drop_stream(cursor, stream_name)
 
     messages = [b"second message", b"third message"]
     for message in messages:
-        kafka_producer.send(kafka_topics[0], message).get(timeout=60)
+        kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     for message in messages:
         vertices_with_msg = common.execute_and_fetch_all(
@@ -103,11 +92,8 @@ def test_start_from_last_committed_offset(kafka_producer, kafka_topics, connecti
 
         assert len(vertices_with_msg) == 0
 
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM test TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple",
-    )
-    common.start_stream(cursor, "test")
+    common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.simple")
+    common.start_stream(cursor, stream_name)
 
     for message in messages:
         common.kafka_check_vertex_exists_with_topic_and_payload(cursor, kafka_topics[0], message)
@@ -118,32 +104,26 @@ def test_check_stream(kafka_producer, kafka_topics, connection, transformation):
     assert len(kafka_topics) > 0
     BATCH_SIZE = 1
     INDEX_OF_FIRST_BATCH = 0
-    cursor = connection.cursor()
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM test TOPICS {kafka_topics[0]} TRANSFORM {transformation} BATCH_SIZE {BATCH_SIZE}",
-    )
-    common.start_stream(cursor, "test")
-    time.sleep(1)
+    stream_name = "test_check_stream_" + transformation.split(".")[1]
 
-    kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60)
-    common.stop_stream(cursor, "test")
+    cursor = connection.cursor()
+
+    common.create_stream(cursor, stream_name, kafka_topics[0], transformation, batch_size=BATCH_SIZE)
+    common.start_stream(cursor, stream_name)
+    kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
+    common.stop_stream(cursor, stream_name)
 
     messages = [b"first message", b"second message", b"third message"]
     for message in messages:
-        kafka_producer.send(kafka_topics[0], message).get(timeout=60)
+        kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     def check_check_stream(batch_limit):
-        assert transformation == "kafka_transform.simple" or transformation == "kafka_transform.with_parameters"
-        test_results = common.execute_and_fetch_all(cursor, f"CHECK STREAM test BATCH_LIMIT {batch_limit}")
+        test_results = common.execute_and_fetch_all(cursor, f"CHECK STREAM {stream_name} BATCH_LIMIT {batch_limit}")
         assert len(test_results) == batch_limit
 
         for i in range(batch_limit):
             message_as_str = messages[i].decode("utf-8")
-            assert (
-                BATCH_SIZE == 1
-            )  # If batch size != 1, then the usage of INDEX_OF_FIRST_BATCH must change: the result will have a list of queries (pair<parameters,query>)
-
+            # If batch size != 1, then the usage of INDEX_OF_FIRST_BATCH must change: the result will have a list of queries (pair<parameters,query>)
             if transformation == "kafka_transform.simple":
                 assert (
                     f"payload: '{message_as_str}'"
@@ -165,41 +145,48 @@ def test_check_stream(kafka_producer, kafka_topics, connection, transformation):
     check_check_stream(1)
     check_check_stream(2)
     check_check_stream(3)
-    common.start_stream(cursor, "test")
+    common.start_stream(cursor, stream_name)
 
     for message in messages:
         common.kafka_check_vertex_exists_with_topic_and_payload(cursor, kafka_topics[0], message)
 
 
-def test_show_streams(kafka_producer, kafka_topics, connection):
+def test_show_streams(kafka_topics, connection):
     assert len(kafka_topics) > 1
     cursor = connection.cursor()
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM default_values TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BOOTSTRAP_SERVERS 'localhost:9092'",
-    )
 
     consumer_group = "my_special_consumer_group"
     BATCH_INTERVAL = 42
     BATCH_SIZE = 3
-    common.execute_and_fetch_all(
+    default_values_stream = "default_values"
+    complex_values_stream = "complex_values"
+
+    common.create_stream(
+        cursor, default_values_stream, kafka_topics[0], "kafka_transform.simple", bootstrap_servers="'localhost:29092'"
+    )
+    common.create_stream(
         cursor,
-        f"CREATE KAFKA STREAM complex_values TOPICS {','.join(kafka_topics)} TRANSFORM kafka_transform.with_parameters CONSUMER_GROUP {consumer_group} BATCH_INTERVAL {BATCH_INTERVAL} BATCH_SIZE {BATCH_SIZE} ",
+        complex_values_stream,
+        ",".join(kafka_topics),
+        "kafka_transform.with_parameters",
+        consumer_group=consumer_group,
+        batch_interval=BATCH_INTERVAL,
+        batch_size=BATCH_SIZE,
     )
 
     assert len(common.execute_and_fetch_all(cursor, "SHOW STREAMS")) == 2
 
     common.check_stream_info(
         cursor,
-        "default_values",
-        ("default_values", "kafka", 100, 1000, "kafka_transform.simple", None, False),
+        default_values_stream,
+        (default_values_stream, "kafka", 100, 1000, "kafka_transform.simple", None, False),
     )
 
     common.check_stream_info(
         cursor,
-        "complex_values",
+        complex_values_stream,
         (
-            "complex_values",
+            complex_values_stream,
             "kafka",
             BATCH_INTERVAL,
             BATCH_SIZE,
@@ -219,7 +206,7 @@ def test_start_and_stop_during_check(kafka_producer, kafka_topics, connection, o
         return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BATCH_SIZE {BATCH_SIZE}"
 
     def message_sender(msg):
-        kafka_producer.send(kafka_topics[0], msg).get(timeout=60)
+        kafka_producer.send(kafka_topics[0], msg).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     common.test_start_and_stop_during_check(
         operation,
@@ -235,14 +222,12 @@ def test_check_already_started_stream(kafka_topics, connection):
     assert len(kafka_topics) > 0
     cursor = connection.cursor()
 
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM started_stream TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple",
-    )
-    common.start_stream(cursor, "started_stream")
+    stream_name = "test_check_already_started_stream"
+    common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.simple")
+    common.start_stream(cursor, stream_name)
 
     with pytest.raises(mgclient.DatabaseError):
-        common.execute_and_fetch_all(cursor, "CHECK STREAM started_stream")
+        common.execute_and_fetch_all(cursor, f"CHECK STREAM {stream_name}")
 
 
 def test_start_checked_stream_after_timeout(kafka_topics, connection):
@@ -254,19 +239,14 @@ def test_start_checked_stream_after_timeout(kafka_topics, connection):
 
 def test_restart_after_error(kafka_producer, kafka_topics, connection):
     cursor = connection.cursor()
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM test_stream TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.query",
-    )
+    stream_name = "test_restart_after_error"
+    common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.query")
+    common.start_stream(cursor, stream_name)
 
-    common.start_stream(cursor, "test_stream")
-    time.sleep(1)
+    kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
+    assert common.timed_wait(lambda: not common.get_is_running(cursor, stream_name))
 
-    kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60)
-    assert common.timed_wait(lambda: not common.get_is_running(cursor, "test_stream"))
-
-    common.start_stream(cursor, "test_stream")
-    time.sleep(1)
+    common.start_stream(cursor, stream_name)
     kafka_producer.send(kafka_topics[0], b"CREATE (n:VERTEX { id : 42 })")
     assert common.check_one_result_row(cursor, "MATCH (n:VERTEX { id : 42 }) RETURN n")
 
@@ -275,23 +255,21 @@ def test_restart_after_error(kafka_producer, kafka_topics, connection):
 def test_bootstrap_server(kafka_producer, kafka_topics, connection, transformation):
     assert len(kafka_topics) > 0
     cursor = connection.cursor()
-    LOCAL = "localhost:9092"
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE KAFKA STREAM test TOPICS {','.join(kafka_topics)} TRANSFORM {transformation} BOOTSTRAP_SERVERS '{LOCAL}'",
-    )
-    common.start_stream(cursor, "test")
-    time.sleep(5)
+    local = "'localhost:29092'"
+    stream_name = "test_bootstrap_server_" + transformation.split(".")[1]
+
+    common.create_stream(cursor, stream_name, ",".join(kafka_topics), transformation, bootstrap_servers=local)
+    common.start_stream(cursor, stream_name)
 
     for topic in kafka_topics:
-        kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=60)
+        kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     for topic in kafka_topics:
         common.kafka_check_vertex_exists_with_topic_and_payload(cursor, topic, common.SIMPLE_MSG)
 
 
 @pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
-def test_bootstrap_server_empty(kafka_producer, kafka_topics, connection, transformation):
+def test_bootstrap_server_empty(kafka_topics, connection, transformation):
     assert len(kafka_topics) > 0
     cursor = connection.cursor()
     with pytest.raises(mgclient.DatabaseError):
@@ -312,7 +290,7 @@ def test_set_offset(kafka_producer, kafka_topics, connection, transformation):
 
     messages = [f"{i} message" for i in range(1, 21)]
     for message in messages:
-        kafka_producer.send(kafka_topics[0], message.encode()).get(timeout=60)
+        kafka_producer.send(kafka_topics[0], message.encode()).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     def consume(expected_msgs):
         common.start_stream(cursor, "test")
@@ -352,7 +330,7 @@ def test_set_offset(kafka_producer, kafka_topics, connection, transformation):
     res = execute_set_offset_and_consume(-2, [])
     assert len(res) == 0
     last_msg = "Final Message"
-    kafka_producer.send(kafka_topics[0], last_msg.encode()).get(timeout=60)
+    kafka_producer.send(kafka_topics[0], last_msg.encode()).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
     res = consume([last_msg])
     assert len(res) == 1
     assert comparison_check("Final Message", res[0])
@@ -361,21 +339,27 @@ def test_set_offset(kafka_producer, kafka_topics, connection, transformation):
 
 def test_info_procedure(kafka_topics, connection):
     cursor = connection.cursor()
-    STREAM_NAME = "test_stream"
-    CONFIGS = {"sasl.username": "michael.scott"}
-    LOCAL = "localhost:9092"
-    CREDENTIALS = {"sasl.password": "S3cr3tP4ssw0rd"}
-    CONSUMER_GROUP = "ConsumerGr"
-    common.execute_and_fetch_all(
+    stream_name = "test_stream"
+    configs = {"sasl.username": "michael.scott"}
+    local = "localhost:29092"
+    credentials = {"sasl.password": "S3cr3tP4ssw0rd"}
+    consumer_group = "ConsumerGr"
+
+    common.create_stream(
         cursor,
-        f"CREATE KAFKA STREAM {STREAM_NAME} TOPICS {','.join(kafka_topics)} TRANSFORM kafka_transform.simple CONSUMER_GROUP {CONSUMER_GROUP} BOOTSTRAP_SERVERS '{LOCAL}' CONFIGS {CONFIGS} CREDENTIALS {CREDENTIALS}",
+        stream_name,
+        ",".join(kafka_topics),
+        "kafka_transform.simple",
+        consumer_group=consumer_group,
+        bootstrap_servers=f"'{local}'",
+        configs=configs,
+        credentials=credentials,
     )
+    stream_info = common.execute_and_fetch_all(cursor, f"CALL mg.kafka_stream_info('{stream_name}') YIELD *")
 
-    stream_info = common.execute_and_fetch_all(cursor, f"CALL mg.kafka_stream_info('{STREAM_NAME}') YIELD *")
+    reducted_credentials = {key: "<REDUCTED>" for key in credentials.keys()}
 
-    reducted_credentials = {key: "<REDUCTED>" for key in CREDENTIALS.keys()}
-
-    expected_stream_info = [(LOCAL, CONFIGS, CONSUMER_GROUP, reducted_credentials, kafka_topics)]
+    expected_stream_info = [(local, configs, consumer_group, reducted_credentials, kafka_topics)]
     common.validate_info(stream_info, expected_stream_info)
 
 
@@ -398,7 +382,7 @@ def test_check_stream_same_number_of_queries_than_messages(kafka_producer, kafka
         return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size}"
 
     def message_sender(msg):
-        kafka_producer.send(kafka_topics[0], msg).get(timeout=60)
+        kafka_producer.send(kafka_topics[0], msg).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     common.test_check_stream_same_number_of_queries_than_messages(connection, stream_creator, message_sender)
 
@@ -409,30 +393,33 @@ def test_check_stream_different_number_of_queries_than_messages(kafka_producer,
     TRANSFORMATION = "common_transform.check_stream_with_filtering"
 
     def stream_creator(stream_name, batch_size):
-        return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000  BATCH_SIZE {batch_size}"
+        return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size}"
 
     def message_sender(msg):
-        kafka_producer.send(kafka_topics[0], msg).get(timeout=60)
+        kafka_producer.send(kafka_topics[0], msg).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     common.test_check_stream_different_number_of_queries_than_messages(connection, stream_creator, message_sender)
 
 
 def test_start_stream_with_batch_limit(kafka_producer, kafka_topics, connection):
     assert len(kafka_topics) > 0
+    STREAM_NAME = "test_start_stream_with_batch_limit"
 
-    def stream_creator(stream_name):
+    def stream_creator():
         return (
-            f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BATCH_SIZE 1"
+            f"CREATE KAFKA STREAM {STREAM_NAME} TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BATCH_SIZE 1"
         )
 
     def messages_sender(nof_messages):
-        for x in range(nof_messages):
-            kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60)
+        for _ in range(nof_messages):
+            kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(
+                timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT
+            )
 
-    common.test_start_stream_with_batch_limit(connection, stream_creator, messages_sender)
+    common.test_start_stream_with_batch_limit(connection, STREAM_NAME, stream_creator, messages_sender)
 
 
-def test_start_stream_with_batch_limit_timeout(kafka_producer, kafka_topics, connection):
+def test_start_stream_with_batch_limit_timeout(kafka_topics, connection):
     assert len(kafka_topics) > 0
 
     def stream_creator(stream_name):
@@ -443,7 +430,7 @@ def test_start_stream_with_batch_limit_timeout(kafka_producer, kafka_topics, con
     common.test_start_stream_with_batch_limit_timeout(connection, stream_creator)
 
 
-def test_start_stream_with_batch_limit_reaching_timeout(kafka_producer, kafka_topics, connection):
+def test_start_stream_with_batch_limit_reaching_timeout(kafka_topics, connection):
     assert len(kafka_topics) > 0
 
     def stream_creator(stream_name, batch_size):
@@ -461,7 +448,7 @@ def test_start_stream_with_batch_limit_while_check_running(kafka_producer, kafka
         )
 
     def message_sender(message):
-        kafka_producer.send(kafka_topics[0], message).get(timeout=6000)
+        kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     def setup_function(start_check_stream, cursor, stream_name, batch_limit, timeout):
         thread_stream_check = Process(target=start_check_stream, daemon=True, args=(stream_name, batch_limit, timeout))
@@ -488,12 +475,12 @@ def test_check_while_stream_with_batch_limit_running(kafka_producer, kafka_topic
         )
 
     def message_sender(message):
-        kafka_producer.send(kafka_topics[0], message).get(timeout=6000)
+        kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT)
 
     common.test_check_while_stream_with_batch_limit_running(connection, stream_creator, message_sender)
 
 
-def test_start_stream_with_batch_limit_with_invalid_batch_limit(kafka_producer, kafka_topics, connection):
+def test_start_stream_with_batch_limit_with_invalid_batch_limit(kafka_topics, connection):
     assert len(kafka_topics) > 0
 
     def stream_creator(stream_name):
@@ -504,7 +491,7 @@ def test_start_stream_with_batch_limit_with_invalid_batch_limit(kafka_producer,
     common.test_start_stream_with_batch_limit_with_invalid_batch_limit(connection, stream_creator)
 
 
-def test_check_stream_with_batch_limit_with_invalid_batch_limit(kafka_producer, kafka_topics, connection):
+def test_check_stream_with_batch_limit_with_invalid_batch_limit(kafka_topics, connection):
     assert len(kafka_topics) > 0
 
     def stream_creator(stream_name):
diff --git a/tests/e2e/streams/pulsar.yml b/tests/e2e/streams/pulsar.yml
deleted file mode 100644
index 31241ad66..000000000
--- a/tests/e2e/streams/pulsar.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-version: '3.7'
-services:
-  pulsar:
-    image: 'apachepulsar/pulsar:latest'
-    ports:
-      - '6652:8080'
-      - '6650:6650'
-    entrypoint: ['bin/pulsar', 'standalone']
diff --git a/tests/e2e/streams/pulsar/docker-compose.yml b/tests/e2e/streams/pulsar/docker-compose.yml
new file mode 100644
index 000000000..a490fbdb1
--- /dev/null
+++ b/tests/e2e/streams/pulsar/docker-compose.yml
@@ -0,0 +1,9 @@
+version: "3"
+
+services:
+  pulsar:
+    image: 'apachepulsar/pulsar:2.8.1'
+    ports:
+      - '6652:8080'
+      - '6650:6650'
+    entrypoint: [ 'bin/pulsar', 'standalone' ]
diff --git a/tests/e2e/streams/pulsar_streams_tests.py b/tests/e2e/streams/pulsar_streams_tests.py
index f68bf14d6..cf52416cb 100755
--- a/tests/e2e/streams/pulsar_streams_tests.py
+++ b/tests/e2e/streams/pulsar_streams_tests.py
@@ -12,11 +12,12 @@
 # licenses/APL.txt.
 
 import sys
-import pytest
-import mgclient
 import time
 from multiprocessing import Process, Value
+
 import common
+import mgclient
+import pytest
 
 TRANSFORMATIONS_TO_CHECK = ["pulsar_transform.simple", "pulsar_transform.with_parameters"]
 
@@ -80,12 +81,6 @@ def test_start_from_latest_messages(pulsar_client, pulsar_topics, connection):
     # inbetween should be lost. Additionally, we check that consumer continues from the correct message
     # after stopping and starting again.
     assert len(pulsar_topics) > 0
-    cursor = connection.cursor()
-    common.execute_and_fetch_all(
-        cursor,
-        f"CREATE PULSAR STREAM test TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple",
-    )
-    common.start_stream(cursor, "test")
 
     def assert_message_not_consumed(message):
         vertices_with_msg = common.execute_and_fetch_all(
@@ -95,6 +90,13 @@ def test_start_from_latest_messages(pulsar_client, pulsar_topics, connection):
 
         assert len(vertices_with_msg) == 0
 
+    cursor = connection.cursor()
+    common.execute_and_fetch_all(
+        cursor,
+        f"CREATE PULSAR STREAM test TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple",
+    )
+    common.start_stream(cursor, "test")
+
     producer = pulsar_client.create_producer(
         common.pulsar_default_namespace_topic(pulsar_topics[0]), send_timeout_millis=60000
     )
@@ -131,7 +133,7 @@ def test_start_from_latest_messages(pulsar_client, pulsar_topics, connection):
         producer.send(message)
         assert_message_not_consumed(message)
 
-    common.start_stream(cursor, "test")
+    common.start_stream(cursor, "test", sleep=False)
 
     assert_message_not_consumed(LOST_MESSAGE)
 
@@ -338,19 +340,20 @@ def test_service_url(pulsar_client, pulsar_topics, connection, transformation):
 
 def test_start_stream_with_batch_limit(pulsar_client, pulsar_topics, connection):
     assert len(pulsar_topics) > 1
+    STREAM_NAME = "test_start_stream_with_batch_limit"
 
-    def stream_creator(stream_name):
-        return f"CREATE PULSAR STREAM {stream_name} TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple BATCH_SIZE 1"
+    def stream_creator():
+        return f"CREATE PULSAR STREAM {STREAM_NAME} TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple BATCH_SIZE 1"
 
     producer = pulsar_client.create_producer(
         common.pulsar_default_namespace_topic(pulsar_topics[0]), send_timeout_millis=60000
     )
 
     def messages_sender(nof_messages):
-        for x in range(nof_messages):
+        for _ in range(nof_messages):
             producer.send(common.SIMPLE_MSG)
 
-    common.test_start_stream_with_batch_limit(connection, stream_creator, messages_sender)
+    common.test_start_stream_with_batch_limit(connection, STREAM_NAME, stream_creator, messages_sender)
 
 
 def test_start_stream_with_batch_limit_timeout(pulsar_client, pulsar_topics, connection):
@@ -409,7 +412,7 @@ def test_check_stream_same_number_of_queries_than_messages(pulsar_client, pulsar
     TRANSFORMATION = "common_transform.check_stream_no_filtering"
 
     def stream_creator(stream_name, batch_size):
-        return f"CREATE PULSAR STREAM {stream_name} TOPICS {pulsar_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE  {batch_size} "
+        return f"CREATE PULSAR STREAM {stream_name} TOPICS {pulsar_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size} "
 
     producer = pulsar_client.create_producer(
         common.pulsar_default_namespace_topic(pulsar_topics[0]), send_timeout_millis=60000
diff --git a/tests/e2e/temporal_types/CMakeLists.txt b/tests/e2e/temporal_types/CMakeLists.txt
index aad9561fe..dac9c2000 100644
--- a/tests/e2e/temporal_types/CMakeLists.txt
+++ b/tests/e2e/temporal_types/CMakeLists.txt
@@ -4,3 +4,4 @@ find_package(gflags REQUIRED)
 add_executable(memgraph__e2e__temporal_roundtrip roundtrip.cpp)
 target_link_libraries(memgraph__e2e__temporal_roundtrip PUBLIC mgclient mg-utils gflags)
 
+copy_e2e_files(temporal_roundtrip workloads.yaml)
diff --git a/tests/e2e/transaction_queue/CMakeLists.txt b/tests/e2e/transaction_queue/CMakeLists.txt
index 574c46bfd..f2e7db170 100644
--- a/tests/e2e/transaction_queue/CMakeLists.txt
+++ b/tests/e2e/transaction_queue/CMakeLists.txt
@@ -6,3 +6,5 @@ copy_query_modules_reloading_procedures_e2e_python_files(common.py)
 copy_query_modules_reloading_procedures_e2e_python_files(test_transaction_queue.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(transaction_queue workloads.yaml)
diff --git a/tests/e2e/transaction_rollback/CMakeLists.txt b/tests/e2e/transaction_rollback/CMakeLists.txt
index a64d3bfeb..4b9fd289f 100644
--- a/tests/e2e/transaction_rollback/CMakeLists.txt
+++ b/tests/e2e/transaction_rollback/CMakeLists.txt
@@ -7,3 +7,5 @@ transaction_rollback_e2e_python_files(conftest.py)
 transaction_rollback_e2e_python_files(transaction.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(transaction_rollback workloads.yaml)
diff --git a/tests/e2e/triggers/CMakeLists.txt b/tests/e2e/triggers/CMakeLists.txt
index 7b540d59f..8f5fe7676 100644
--- a/tests/e2e/triggers/CMakeLists.txt
+++ b/tests/e2e/triggers/CMakeLists.txt
@@ -27,3 +27,5 @@ endfunction()
 
 copy_triggers_e2e_python_files(common.py)
 copy_triggers_e2e_python_files(triggers_properties_false.py)
+
+copy_e2e_files(triggers workloads.yaml)
diff --git a/tests/e2e/write_procedures/CMakeLists.txt b/tests/e2e/write_procedures/CMakeLists.txt
index 27a9a73e2..f7dc2d8b3 100644
--- a/tests/e2e/write_procedures/CMakeLists.txt
+++ b/tests/e2e/write_procedures/CMakeLists.txt
@@ -8,3 +8,5 @@ copy_write_procedures_e2e_python_files(simple_write.py)
 copy_write_procedures_e2e_python_files(read_subgraph.py)
 
 add_subdirectory(procedures)
+
+copy_e2e_files(write_procedures workloads.yaml)
diff --git a/tests/gql_behave/tests/memgraph_V1/features/functions.feature b/tests/gql_behave/tests/memgraph_V1/features/functions.feature
index bc9121676..19a7f2332 100644
--- a/tests/gql_behave/tests/memgraph_V1/features/functions.feature
+++ b/tests/gql_behave/tests/memgraph_V1/features/functions.feature
@@ -60,6 +60,34 @@ Feature: Functions
             | false |
             | true  |
 
+    Scenario: ToBoolean test 03:
+        Given an empty graph
+        And having executed
+            """
+            CREATE (:Node {prop: ToBoolean("t")});
+            """
+        When executing query:
+            """
+            MATCH (n:Node) RETURN n.prop;
+            """
+        Then the result should be:
+            | n.prop |
+            | true   |
+
+    Scenario: ToBoolean test 03:
+        Given an empty graph
+        And having executed
+            """
+            CREATE (:Node {prop: ToBoolean("f")});
+            """
+        When executing query:
+            """
+            MATCH (n:Node) RETURN n.prop;
+            """
+        Then the result should be:
+            | n.prop |
+            | false  |
+
     Scenario: ToInteger test 01:
         Given an empty graph
         And having executed
diff --git a/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature b/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature
index bfe6b6225..8c5538d6b 100644
--- a/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature
+++ b/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature
@@ -279,3 +279,15 @@ Feature: List operators
         Then the result should be:
             | o                                       |
             | (:Node {Status: 'This is the status'})  |
+
+     Scenario: Simple list pattern comprehension
+        Given graph "graph_keanu"
+        When executing query:
+            """
+            MATCH (keanu:Person {name: 'Keanu Reeves'})
+            RETURN [(keanu)-->(b:Movie) WHERE b.title CONTAINS 'Matrix' | b.released] AS years
+            """
+        Then an error should be raised
+#        Then the result should be:
+#            | years                 |
+#            | [2021,2003,2003,1999] |
diff --git a/tests/gql_behave/tests/memgraph_V1/features/match.feature b/tests/gql_behave/tests/memgraph_V1/features/match.feature
index 227ad9ad6..0d0477ad9 100644
--- a/tests/gql_behave/tests/memgraph_V1/features/match.feature
+++ b/tests/gql_behave/tests/memgraph_V1/features/match.feature
@@ -771,3 +771,17 @@ Feature: Match
         Then the result should be:
             | path                                        |
             | <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> |
+
+    Scenario: Using path indentifier from CREATE in MERGE
+        Given an empty graph
+        And having executed:
+            """
+            CREATE p0=()-[:T0]->() MERGE ({k:(size(p0))});
+            """
+        When executing query:
+            """
+            MATCH (n {k: 1}) RETURN n;
+            """
+        Then the result should be:
+            | n        |
+            | ({k: 1}) |
diff --git a/tests/gql_behave/tests/memgraph_V1/features/parameters.feature b/tests/gql_behave/tests/memgraph_V1/features/parameters.feature
index 908507e43..d9b25ff8c 100644
--- a/tests/gql_behave/tests/memgraph_V1/features/parameters.feature
+++ b/tests/gql_behave/tests/memgraph_V1/features/parameters.feature
@@ -52,7 +52,7 @@ Feature: Parameters
             |   [1, 2, 3]    |
 
     Scenario: Parameters in match:
-	Given an empty graph
+	    Given an empty graph
         And having executed:
             """
             CREATE (a {x : 10})
@@ -66,3 +66,107 @@ Feature: Parameters
         Then the result should be:
             | a.x |
             | 10  |
+
+    Scenario: Label parameters in match:
+        Given an empty graph
+        And having executed:
+            """
+            CREATE (a:Label1 {x : 10})
+            """
+        And parameters are:
+            | a     | 10     |
+            | label | Label1 |
+        When executing query:
+            """
+            MATCH (a:$label {x : $a}) RETURN a
+            """
+        Then the result should be:
+            | a                |
+            | (:Label1{x: 10}) |
+
+    Scenario: Label parameters in create and match
+        Given an empty graph
+        And parameters are:
+            | a     | 10     |
+            | label | Label1 |
+        When executing query:
+            """
+            CREATE (a:$label {x: $a})
+            """
+        When executing query:
+            """
+            MATCH (a:$label {x: $a}) RETURN a
+            """
+        Then the result should be:
+            | a                |
+            | (:Label1{x: 10}) |
+
+    Scenario: Label parameters in merge
+        Given an empty graph
+        And parameters are:
+            | a     | 10     |
+            | label | Label1 |
+        When executing query:
+            """
+            MERGE (a:$label {x: $a}) RETURN a
+            """
+        Then the result should be:
+            | a                |
+            | (:Label1{x: 10}) |
+
+    Scenario: Label parameters in set label
+        Given an empty graph
+        And having executed:
+            """
+            CREATE (a:Label1 {x : 10})
+            """
+        And parameters are:
+            | new_label | Label2 |
+        When executing query:
+            """
+            MATCH (a:Label1 {x: 10}) SET a:$new_label
+            """
+        When executing query:
+            """
+            MATCH (a:Label1:Label1 {x: 10}) RETURN a
+            """
+        Then the result should be:
+            | a                        |
+            | (:Label1:Label2 {x: 10}) |
+
+    Scenario: Label parameters in remove label
+        Given an empty graph
+        And having executed:
+            """
+            CREATE (a:Label1:LabelToRemove {x : 10})
+            """
+        And parameters are:
+            | label_to_remove | LabelToRemove |
+        When executing query:
+            """
+            MATCH (a {x: 10}) REMOVE a:$label_to_remove
+            """
+        When executing query:
+            """
+            MATCH (a {x: 10}) RETURN a
+            """
+        Then the result should be:
+            | a                 |
+            | (:Label1 {x: 10}) |
+
+    Scenario: Parameters for limit in return returnBody
+        Given an empty graph
+        And having executed:
+            """
+            FOREACH (id IN range(1, 10) | CREATE (:Node {id: id}))
+            """
+        And parameters are:
+            | limit | 2 |
+        When executing query:
+            """
+            MATCH (n) RETURN n LIMIT $limit
+            """
+        Then the result should be:
+            | n               |
+            | (:Node {id: 1}) |
+            | (:Node {id: 2}) |
diff --git a/tests/gql_behave/tests/memgraph_V1/graphs/graph_keanu.cypher b/tests/gql_behave/tests/memgraph_V1/graphs/graph_keanu.cypher
new file mode 100644
index 000000000..a7a72aced
--- /dev/null
+++ b/tests/gql_behave/tests/memgraph_V1/graphs/graph_keanu.cypher
@@ -0,0 +1,16 @@
+CREATE
+  (keanu:Person {name: 'Keanu Reeves'}),
+  (johnnyMnemonic:Movie {title: 'Johnny Mnemonic', released: 1995}),
+  (theMatrixRevolutions:Movie {title: 'The Matrix Revolutions', released: 2003}),
+  (theMatrixReloaded:Movie {title: 'The Matrix Reloaded', released: 2003}),
+  (theReplacements:Movie {title: 'The Replacements', released: 2000}),
+  (theMatrix:Movie {title: 'The Matrix', released: 1999}),
+  (theDevilsAdvocate:Movie {title: 'The Devils Advocate', released: 1997}),
+  (theMatrixResurrections:Movie {title: 'The Matrix Resurrections', released: 2021}),
+  (keanu)-[:ACTED_IN]->(johnnyMnemonic),
+  (keanu)-[:ACTED_IN]->(theMatrixRevolutions),
+  (keanu)-[:ACTED_IN]->(theMatrixReloaded),
+  (keanu)-[:ACTED_IN]->(theReplacements),
+  (keanu)-[:ACTED_IN]->(theMatrix),
+  (keanu)-[:ACTED_IN]->(theDevilsAdvocate),
+  (keanu)-[:ACTED_IN]->(theMatrixResurrections);
diff --git a/tests/integration/telemetry/client.cpp b/tests/integration/telemetry/client.cpp
index 34e1c2a67..b93b1ada5 100644
--- a/tests/integration/telemetry/client.cpp
+++ b/tests/integration/telemetry/client.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -13,6 +13,7 @@
 
 #include "dbms/dbms_handler.hpp"
 #include "glue/auth_checker.hpp"
+#include "glue/auth_global.hpp"
 #include "glue/auth_handler.hpp"
 #include "requests/requests.hpp"
 #include "storage/v2/config.hpp"
@@ -32,9 +33,10 @@ int main(int argc, char **argv) {
 
   // Memgraph backend
   std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_telemetry_integration_test"};
-  memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{data_directory /
-                                                                                                     "auth"};
-  memgraph::glue::AuthQueryHandler auth_handler(&auth_, "");
+  memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{
+      data_directory / "auth",
+      memgraph::auth::Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, "", true}};
+  memgraph::glue::AuthQueryHandler auth_handler(&auth_);
   memgraph::glue::AuthChecker auth_checker(&auth_);
 
   memgraph::storage::Config db_config;
@@ -44,7 +46,7 @@ int main(int argc, char **argv) {
   memgraph::dbms::DbmsHandler dbms_handler(db_config
 #ifdef MG_ENTERPRISE
                                            ,
-                                           &auth_, false, false
+                                           &auth_, false
 #endif
   );
   memgraph::query::InterpreterContext interpreter_context_({}, &dbms_handler, &repl_state, &auth_handler,
diff --git a/tests/jepsen/run.sh b/tests/jepsen/run.sh
index b366e7846..a1587c8a1 100755
--- a/tests/jepsen/run.sh
+++ b/tests/jepsen/run.sh
@@ -24,7 +24,7 @@ PRINT_CONTEXT() {
 
 HELP_EXIT() {
     echo ""
-    echo "HELP: $0 help|cluster-up|cluster-cleanup|cluster-dealloc|mgbuild|test|test-all-individually [args]"
+    echo "HELP: $0 help|cluster-up|cluster-refresh|cluster-cleanup|cluster-dealloc|mgbuild|test|test-all-individually [args]"
     echo ""
     echo "    test args --binary                 MEMGRAPH_BINARY_PATH"
     echo "              --ignore-run-stdout-logs Ignore lein run stdout logs."
@@ -184,6 +184,37 @@ PROCESS_RESULTS() {
     INFO "Result processing (printing and packing) DONE."
 }
 
+CLUSTER_UP() {
+  PRINT_CONTEXT
+  "$script_dir/jepsen/docker/bin/up" --daemon
+  sleep 10
+  # Ensure all SSH connections between Jepsen containers work
+  for node in $(docker ps --filter name=jepsen* --filter status=running --format "{{.Names}}"); do
+      if [ "$node" == "jepsen-control" ]; then
+          continue
+      fi
+      node_hostname="${node##jepsen-}"
+      docker exec jepsen-control bash -c "ssh -oStrictHostKeyChecking=no -t $node_hostname exit"
+  done
+}
+
+CLUSTER_DEALLOC() {
+  ps=$(docker ps --filter name=jepsen* --filter status=running -q)
+  if [[ ! -z ${ps} ]]; then
+      echo "Killing ${ps}"
+      docker rm -f ${ps}
+      imgs=$(docker images "jepsen*" -q)
+      if [[ ! -z ${imgs} ]]; then
+          echo "Removing ${imgs}"
+          docker images "jepsen*" -q | xargs docker image rmi -f
+      else
+          echo "No Jepsen images detected!"
+      fi
+  else
+      echo "No Jepsen containers detected!"
+  fi
+}
+
 # Initialize testing context by copying source/binary files. Inside CI,
 # Memgraph is tested on a single machine cluster based on Docker containers.
 # Once these tests will be part of the official Jepsen repo, the majority of
@@ -196,8 +227,16 @@ case $1 in
     # the current cluster is broken because it relies on the folder. That can
     # happen easiliy because the jepsen folder is git ignored.
     cluster-up)
-        PRINT_CONTEXT
-        "$script_dir/jepsen/docker/bin/up" --daemon
+        CLUSTER_UP
+    ;;
+
+    cluster-refresh)
+        CLUSTER_DEALLOC
+        CLUSTER_UP
+    ;;
+
+    cluster-dealloc)
+        CLUSTER_DEALLOC
     ;;
 
     cluster-cleanup)
@@ -212,23 +251,6 @@ case $1 in
         done
     ;;
 
-    cluster-dealloc)
-        ps=$(docker ps --filter name=jepsen* --filter status=running -q)
-        if [[ ! -z ${ps} ]]; then
-            echo "Killing ${ps}"
-            docker rm -f ${ps}
-            imgs=$(docker images "jepsen*" -q)
-            if [[ ! -z ${imgs} ]]; then
-                echo "Removing ${imgs}"
-                docker images "jepsen*" -q | xargs docker image rmi -f
-            else
-                echo "No Jepsen images detected!"
-            fi
-        else
-            echo "No Jepsen containers detected!"
-        fi
-    ;;
-
     mgbuild)
         PRINT_CONTEXT
         echo ""
diff --git a/tests/manual/expression_pretty_printer.cpp b/tests/manual/expression_pretty_printer.cpp
index e2d58b350..20757b195 100644
--- a/tests/manual/expression_pretty_printer.cpp
+++ b/tests/manual/expression_pretty_printer.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2023 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 #include "query/frontend/ast/cypher_main_visitor.hpp"
 #include "query/frontend/ast/pretty_print.hpp"
 #include "query/frontend/opencypher/parser.hpp"
+#include "query/parameters.hpp"
 
 std::string AssembleQueryString(const std::string &expression_string) {
   return "return " + expression_string + " as expr";
@@ -24,8 +25,9 @@ std::string AssembleQueryString(const std::string &expression_string) {
 
 memgraph::query::Query *ParseQuery(const std::string &query_string, memgraph::query::AstStorage *ast_storage) {
   memgraph::query::frontend::ParsingContext context;
+  memgraph::query::Parameters parameters;
   memgraph::query::frontend::opencypher::Parser parser(query_string);
-  memgraph::query::frontend::CypherMainVisitor visitor(context, ast_storage);
+  memgraph::query::frontend::CypherMainVisitor visitor(context, ast_storage, &parameters);
 
   visitor.visit(parser.tree());
   return visitor.query();
diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp
index d5da0ba2b..f550b9724 100644
--- a/tests/manual/interactive_planning.cpp
+++ b/tests/manual/interactive_planning.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -434,11 +434,12 @@ void ExaminePlans(memgraph::query::DbAccessor *dba, const memgraph::query::Symbo
 
 memgraph::query::Query *MakeAst(const std::string &query, memgraph::query::AstStorage *storage) {
   memgraph::query::frontend::ParsingContext parsing_context;
+  memgraph::query::Parameters parameters;
   parsing_context.is_query_cached = false;
   // query -> AST
   auto parser = std::make_unique<memgraph::query::frontend::opencypher::Parser>(query);
   // AST -> high level tree
-  memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage);
+  memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage, &parameters);
   visitor.visit(parser->tree());
   return visitor.query();
 }
@@ -462,7 +463,7 @@ auto MakeLogicalPlans(memgraph::query::CypherQuery *query, memgraph::query::AstS
     memgraph::query::AstStorage ast_copy;
     auto unoptimized_plan = plan->Clone(&ast_copy);
     auto rewritten_plan = post_process.Rewrite(std::move(plan), &ctx);
-    double cost = post_process.EstimatePlanCost(rewritten_plan, dba, symbol_table);
+    double cost = post_process.EstimatePlanCost(rewritten_plan, dba, symbol_table).cost;
     interactive_plans.push_back(
         InteractivePlan{std::move(unoptimized_plan), std::move(ast_copy), std::move(rewritten_plan), cost});
   }
diff --git a/tests/manual/query_planner.cpp b/tests/manual/query_planner.cpp
index 700b70a7a..8f2c107bc 100644
--- a/tests/manual/query_planner.cpp
+++ b/tests/manual/query_planner.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -14,14 +14,14 @@
 #include <gflags/gflags.h>
 
 #include "storage/v2/inmemory/storage.hpp"
-
+using memgraph::replication_coordination_glue::ReplicationRole;
 DECLARE_int32(min_log_level);
 
 int main(int argc, char *argv[]) {
   gflags::ParseCommandLineFlags(&argc, &argv, true);
   spdlog::set_level(spdlog::level::err);
   std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage());
-  auto storage_dba = db->Access();
+  auto storage_dba = db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   RunInteractivePlanning(&dba);
   return 0;
diff --git a/tests/property_based/random_graph.cpp b/tests/property_based/random_graph.cpp
index 1f12df2a1..097c2dc0e 100644
--- a/tests/property_based/random_graph.cpp
+++ b/tests/property_based/random_graph.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,7 +23,7 @@
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/storage.hpp"
 #include "storage/v2/vertex_accessor.hpp"
-
+using memgraph::replication_coordination_glue::ReplicationRole;
 /**
  * It is possible to run test with custom seed with:
  * RC_PARAMS="seed=1" ./random_graph
@@ -40,7 +40,7 @@ RC_GTEST_PROP(RandomGraph, RandomGraph, (std::vector<std::string> vertex_labels,
   std::unordered_map<memgraph::storage::VertexAccessor, std::string> vertex_label_map;
   std::unordered_map<memgraph::storage::EdgeAccessor, std::string> edge_type_map;
 
-  auto dba = db->Access();
+  auto dba = db->Access(ReplicationRole::MAIN);
 
   for (auto label : vertex_labels) {
     auto vertex_accessor = dba->CreateVertex();
diff --git a/tests/stress/memory_limit.py b/tests/stress/memory_limit.py
index 2b2084484..dd8225584 100644
--- a/tests/stress/memory_limit.py
+++ b/tests/stress/memory_limit.py
@@ -173,39 +173,49 @@ def run_writer(repetition_count: int, sleep_sec: float, worker_id: int) -> int:
     """
     This writer creates lot of nodes on each write.
     Also it checks that query failed if memory limit is tried to be broken
+
+    Return:
+        True if write suceeded
+        False otherwise
     """
 
     session = SessionCache.argument_session(args)
 
-    def create() -> bool:
+    def try_create() -> bool:
         """
-        Returns True if done, False if needs to continue
+        Function tries to create until memory limit is reached
+        Return:
+            True if it can continue creating (OOM not reached)
+            False otherwise
         """
-        memory_tracker_data_before_start = get_tracker_data(session)
-        should_fail = memory_tracker_data_before_start >= 2048
-        failed = False
+        should_continue = True
         try:
             try_execute(
                 session,
                 f"FOREACH (i in range(1,10000) | CREATE (:Node {{prop:'big string or something like that'}}))",
             )
         except Exception as ex:
-            failed = True
             output = str(ex)
-            log.info("Exception in create", output)
-            assert "Memory limit exceeded!" in output
+            memory_over_2048_mb = False
+            memory_tracker_data_after_start = get_tracker_data(session)
+            if memory_tracker_data_after_start:
+                memory_over_2048_mb = memory_tracker_data_after_start >= 2048
+            log.info(
+                "Exception in create, exception output:",
+                output,
+                f"Worker {worker_id} started iteration {curr_repetition}, memory over 2048MB: {memory_over_2048_mb}",
+            )
+            has_oom_happend = "Memory limit exceeded!" in output and memory_over_2048_mb
+            should_continue = not has_oom_happend
 
-        if should_fail:
-            assert failed, "Query should have failed"
-            return False
-        return True
+        return should_continue
 
     curr_repetition = 0
 
     while curr_repetition < repetition_count:
         log.info(f"Worker {worker_id} started iteration {curr_repetition}")
 
-        should_continue = create()
+        should_continue = try_create()
 
         if not should_continue:
             return True
@@ -214,6 +224,7 @@ def run_writer(repetition_count: int, sleep_sec: float, worker_id: int) -> int:
         log.info(f"Worker {worker_id} created chain in iteration {curr_repetition}")
 
         curr_repetition += 1
+    return False
 
 
 def execute_function(worker: Worker) -> Worker:
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 956cba781..6f7b3bbef 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -350,7 +350,7 @@ add_unit_test(storage_v2_wal_file.cpp)
 target_link_libraries(${test_prefix}storage_v2_wal_file mg-storage-v2 storage_test_utils fmt)
 
 add_unit_test(storage_v2_replication.cpp)
-target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 mg-dbms fmt)
+target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 mg-dbms fmt mg-repl_coord_glue)
 
 add_unit_test(storage_v2_isolation_level.cpp)
 target_link_libraries(${test_prefix}storage_v2_isolation_level mg-storage-v2)
@@ -368,7 +368,7 @@ add_unit_test(storage_v2_storage_mode.cpp)
 target_link_libraries(${test_prefix}storage_v2_storage_mode mg-storage-v2 storage_test_utils mg-query mg-glue)
 
 add_unit_test(replication_persistence_helper.cpp)
-target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2)
+target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2 mg-repl_coord_glue)
 
 add_unit_test(auth_checker.cpp)
 target_link_libraries(${test_prefix}auth_checker mg-glue mg-auth)
@@ -389,7 +389,7 @@ endif()
 # Test mg-slk
 if(MG_ENTERPRISE)
   add_unit_test(slk_advanced.cpp)
-  target_link_libraries(${test_prefix}slk_advanced mg-storage-v2)
+  target_link_libraries(${test_prefix}slk_advanced mg-storage-v2 mg-replication mg-coordination mg-repl_coord_glue)
 endif()
 
 add_unit_test(slk_core.cpp)
@@ -415,6 +415,9 @@ if(MG_ENTERPRISE)
 
   add_unit_test_with_custom_main(dbms_handler.cpp)
   target_link_libraries(${test_prefix}dbms_handler mg-query mg-auth mg-glue mg-dbms)
+
+  add_unit_test(multi_tenancy.cpp)
+  target_link_libraries(${test_prefix}multi_tenancy mg-query mg-auth mg-glue mg-dbms)
 else()
   add_unit_test_with_custom_main(dbms_handler_community.cpp)
   target_link_libraries(${test_prefix}dbms_handler_community mg-query mg-auth mg-glue mg-dbms)
diff --git a/tests/unit/auth.cpp b/tests/unit/auth.cpp
index 6dbe20914..bc2947a12 100644
--- a/tests/unit/auth.cpp
+++ b/tests/unit/auth.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -19,6 +19,7 @@
 #include "auth/auth.hpp"
 #include "auth/crypto.hpp"
 #include "auth/models.hpp"
+#include "glue/auth_global.hpp"
 #include "license/license.hpp"
 #include "utils/cast.hpp"
 #include "utils/file.hpp"
@@ -26,90 +27,70 @@
 using namespace memgraph::auth;
 namespace fs = std::filesystem;
 
-DECLARE_bool(auth_password_permit_null);
-DECLARE_string(auth_password_strength_regex);
 DECLARE_string(password_encryption_algorithm);
 
 class AuthWithStorage : public ::testing::Test {
  protected:
   void SetUp() override {
     memgraph::utils::EnsureDir(test_folder_);
-    FLAGS_auth_password_permit_null = true;
-    FLAGS_auth_password_strength_regex = ".+";
-
     memgraph::license::global_license_checker.EnableTesting();
+    auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), auth_config);
   }
 
   void TearDown() override { fs::remove_all(test_folder_); }
 
   fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"};
-
-  Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))};
+  Auth::Config auth_config{};
+  std::optional<Auth> auth{};
 };
 
 TEST_F(AuthWithStorage, AddRole) {
-  ASSERT_TRUE(auth.AddRole("admin"));
-  ASSERT_TRUE(auth.AddRole("user"));
-  ASSERT_FALSE(auth.AddRole("admin"));
+  ASSERT_TRUE(auth->AddRole("admin"));
+  ASSERT_TRUE(auth->AddRole("user"));
+  ASSERT_FALSE(auth->AddRole("admin"));
 }
 
 TEST_F(AuthWithStorage, RemoveRole) {
-  ASSERT_TRUE(auth.AddRole("admin"));
-  ASSERT_TRUE(auth.RemoveRole("admin"));
-  class AuthWithStorage : public ::testing::Test {
-   protected:
-    void SetUp() override {
-      memgraph::utils::EnsureDir(test_folder_);
-      FLAGS_auth_password_permit_null = true;
-      FLAGS_auth_password_strength_regex = ".+";
-
-      memgraph::license::global_license_checker.EnableTesting();
-    }
-
-    void TearDown() override { fs::remove_all(test_folder_); }
-
-    fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"};
-
-    Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))};
-  };
-  ASSERT_FALSE(auth.HasUsers());
-  ASSERT_FALSE(auth.RemoveUser("test2"));
-  ASSERT_FALSE(auth.RemoveUser("test"));
-  ASSERT_FALSE(auth.HasUsers());
+  ASSERT_TRUE(auth->AddRole("admin"));
+  ASSERT_TRUE(auth->RemoveRole("admin"));
+  ASSERT_FALSE(auth->HasUsers());
+  ASSERT_FALSE(auth->RemoveUser("test2"));
+  ASSERT_FALSE(auth->RemoveUser("test"));
+  ASSERT_FALSE(auth->HasUsers());
 }
 
 TEST_F(AuthWithStorage, Authenticate) {
-  ASSERT_FALSE(auth.HasUsers());
+  ASSERT_FALSE(auth->HasUsers());
 
-  auto user = auth.AddUser("test");
+  auto user = auth->AddUser("test");
   ASSERT_NE(user, std::nullopt);
-  ASSERT_TRUE(auth.HasUsers());
+  ASSERT_TRUE(auth->HasUsers());
 
-  ASSERT_TRUE(auth.Authenticate("test", "123"));
+  ASSERT_TRUE(auth->Authenticate("test", "123"));
 
   user->UpdatePassword("123");
-  auth.SaveUser(*user);
+  auth->SaveUser(*user);
 
-  ASSERT_NE(auth.Authenticate("test", "123"), std::nullopt);
+  ASSERT_NE(auth->Authenticate("test", "123"), std::nullopt);
 
-  ASSERT_EQ(auth.Authenticate("test", "456"), std::nullopt);
-  ASSERT_NE(auth.Authenticate("test", "123"), std::nullopt);
+  ASSERT_EQ(auth->Authenticate("test", "456"), std::nullopt);
+  ASSERT_NE(auth->Authenticate("test", "123"), std::nullopt);
 
   user->UpdatePassword();
-  auth.SaveUser(*user);
+  auth->SaveUser(*user);
 
-  ASSERT_NE(auth.Authenticate("test", "123"), std::nullopt);
-  ASSERT_NE(auth.Authenticate("test", "456"), std::nullopt);
+  ASSERT_NE(auth->Authenticate("test", "123"), std::nullopt);
+  ASSERT_NE(auth->Authenticate("test", "456"), std::nullopt);
 
-  ASSERT_EQ(auth.Authenticate("nonexistant", "123"), std::nullopt);
+  ASSERT_EQ(auth->Authenticate("nonexistant", "123"), std::nullopt);
 }
 
 TEST_F(AuthWithStorage, UserRolePermissions) {
-  ASSERT_FALSE(auth.HasUsers());
-  ASSERT_TRUE(auth.AddUser("test"));
-  ASSERT_TRUE(auth.HasUsers());
+  ASSERT_FALSE(auth->HasUsers());
+  ASSERT_TRUE(auth->AddUser("test"));
+  ASSERT_TRUE(auth->HasUsers());
 
-  auto user = auth.GetUser("test");
+  auto user = auth->GetUser("test");
   ASSERT_NE(user, std::nullopt);
 
   // Test initial user permissions.
@@ -130,8 +111,8 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
   ASSERT_EQ(user->permissions(), user->GetPermissions());
 
   // Create role.
-  ASSERT_TRUE(auth.AddRole("admin"));
-  auto role = auth.GetRole("admin");
+  ASSERT_TRUE(auth->AddRole("admin"));
+  auto role = auth->GetRole("admin");
   ASSERT_NE(role, std::nullopt);
 
   // Assign permissions to role and role to user.
@@ -163,11 +144,11 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
 
 #ifdef MG_ENTERPRISE
 TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
-  ASSERT_FALSE(auth.HasUsers());
-  ASSERT_TRUE(auth.AddUser("test"));
-  ASSERT_TRUE(auth.HasUsers());
+  ASSERT_FALSE(auth->HasUsers());
+  ASSERT_TRUE(auth->AddUser("test"));
+  ASSERT_TRUE(auth->HasUsers());
 
-  auto user = auth.GetUser("test");
+  auto user = auth->GetUser("test");
   ASSERT_NE(user, std::nullopt);
 
   // Test initial user fine grained access permissions.
@@ -204,8 +185,8 @@ TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
             user->GetFineGrainedAccessEdgeTypePermissions());
 
   // Create role.
-  ASSERT_TRUE(auth.AddRole("admin"));
-  auto role = auth.GetRole("admin");
+  ASSERT_TRUE(auth->AddRole("admin"));
+  auto role = auth->GetRole("admin");
   ASSERT_NE(role, std::nullopt);
 
   // Grant label and edge type to role and role to user.
@@ -236,44 +217,44 @@ TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
 
 TEST_F(AuthWithStorage, RoleManipulations) {
   {
-    auto user1 = auth.AddUser("user1");
+    auto user1 = auth->AddUser("user1");
     ASSERT_TRUE(user1);
-    auto role1 = auth.AddRole("role1");
+    auto role1 = auth->AddRole("role1");
     ASSERT_TRUE(role1);
     user1->SetRole(*role1);
-    auth.SaveUser(*user1);
+    auth->SaveUser(*user1);
 
-    auto user2 = auth.AddUser("user2");
+    auto user2 = auth->AddUser("user2");
     ASSERT_TRUE(user2);
-    auto role2 = auth.AddRole("role2");
+    auto role2 = auth->AddRole("role2");
     ASSERT_TRUE(role2);
     user2->SetRole(*role2);
-    auth.SaveUser(*user2);
+    auth->SaveUser(*user2);
   }
 
   {
-    auto user1 = auth.GetUser("user1");
+    auto user1 = auth->GetUser("user1");
     ASSERT_TRUE(user1);
     const auto *role1 = user1->role();
     ASSERT_NE(role1, nullptr);
     ASSERT_EQ(role1->rolename(), "role1");
 
-    auto user2 = auth.GetUser("user2");
+    auto user2 = auth->GetUser("user2");
     ASSERT_TRUE(user2);
     const auto *role2 = user2->role();
     ASSERT_NE(role2, nullptr);
     ASSERT_EQ(role2->rolename(), "role2");
   }
 
-  ASSERT_TRUE(auth.RemoveRole("role1"));
+  ASSERT_TRUE(auth->RemoveRole("role1"));
 
   {
-    auto user1 = auth.GetUser("user1");
+    auto user1 = auth->GetUser("user1");
     ASSERT_TRUE(user1);
     const auto *role = user1->role();
     ASSERT_EQ(role, nullptr);
 
-    auto user2 = auth.GetUser("user2");
+    auto user2 = auth->GetUser("user2");
     ASSERT_TRUE(user2);
     const auto *role2 = user2->role();
     ASSERT_NE(role2, nullptr);
@@ -281,17 +262,17 @@ TEST_F(AuthWithStorage, RoleManipulations) {
   }
 
   {
-    auto role1 = auth.AddRole("role1");
+    auto role1 = auth->AddRole("role1");
     ASSERT_TRUE(role1);
   }
 
   {
-    auto user1 = auth.GetUser("user1");
+    auto user1 = auth->GetUser("user1");
     ASSERT_TRUE(user1);
     const auto *role1 = user1->role();
     ASSERT_EQ(role1, nullptr);
 
-    auto user2 = auth.GetUser("user2");
+    auto user2 = auth->GetUser("user2");
     ASSERT_TRUE(user2);
     const auto *role2 = user2->role();
     ASSERT_NE(role2, nullptr);
@@ -299,7 +280,7 @@ TEST_F(AuthWithStorage, RoleManipulations) {
   }
 
   {
-    auto users = auth.AllUsers();
+    auto users = auth->AllUsers();
     std::sort(users.begin(), users.end(), [](const User &a, const User &b) { return a.username() < b.username(); });
     ASSERT_EQ(users.size(), 2);
     ASSERT_EQ(users[0].username(), "user1");
@@ -307,7 +288,7 @@ TEST_F(AuthWithStorage, RoleManipulations) {
   }
 
   {
-    auto roles = auth.AllRoles();
+    auto roles = auth->AllRoles();
     std::sort(roles.begin(), roles.end(), [](const Role &a, const Role &b) { return a.rolename() < b.rolename(); });
     ASSERT_EQ(roles.size(), 2);
     ASSERT_EQ(roles[0].rolename(), "role1");
@@ -315,7 +296,7 @@ TEST_F(AuthWithStorage, RoleManipulations) {
   }
 
   {
-    auto users = auth.AllUsersForRole("role2");
+    auto users = auth->AllUsersForRole("role2");
     ASSERT_EQ(users.size(), 1);
     ASSERT_EQ(users[0].username(), "user2");
   }
@@ -323,16 +304,16 @@ TEST_F(AuthWithStorage, RoleManipulations) {
 
 TEST_F(AuthWithStorage, UserRoleLinkUnlink) {
   {
-    auto user = auth.AddUser("user");
+    auto user = auth->AddUser("user");
     ASSERT_TRUE(user);
-    auto role = auth.AddRole("role");
+    auto role = auth->AddRole("role");
     ASSERT_TRUE(role);
     user->SetRole(*role);
-    auth.SaveUser(*user);
+    auth->SaveUser(*user);
   }
 
   {
-    auto user = auth.GetUser("user");
+    auto user = auth->GetUser("user");
     ASSERT_TRUE(user);
     const auto *role = user->role();
     ASSERT_NE(role, nullptr);
@@ -340,14 +321,14 @@ TEST_F(AuthWithStorage, UserRoleLinkUnlink) {
   }
 
   {
-    auto user = auth.GetUser("user");
+    auto user = auth->GetUser("user");
     ASSERT_TRUE(user);
     user->ClearRole();
-    auth.SaveUser(*user);
+    auth->SaveUser(*user);
   }
 
   {
-    auto user = auth.GetUser("user");
+    auto user = auth->GetUser("user");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->role(), nullptr);
   }
@@ -355,19 +336,19 @@ TEST_F(AuthWithStorage, UserRoleLinkUnlink) {
 
 TEST_F(AuthWithStorage, UserPasswordCreation) {
   {
-    auto user = auth.AddUser("test");
+    auto user = auth->AddUser("test");
     ASSERT_TRUE(user);
-    ASSERT_TRUE(auth.Authenticate("test", "123"));
-    ASSERT_TRUE(auth.Authenticate("test", "456"));
-    ASSERT_TRUE(auth.RemoveUser(user->username()));
+    ASSERT_TRUE(auth->Authenticate("test", "123"));
+    ASSERT_TRUE(auth->Authenticate("test", "456"));
+    ASSERT_TRUE(auth->RemoveUser(user->username()));
   }
 
   {
-    auto user = auth.AddUser("test", "123");
+    auto user = auth->AddUser("test", "123");
     ASSERT_TRUE(user);
-    ASSERT_TRUE(auth.Authenticate("test", "123"));
-    ASSERT_FALSE(auth.Authenticate("test", "456"));
-    ASSERT_TRUE(auth.RemoveUser(user->username()));
+    ASSERT_TRUE(auth->Authenticate("test", "123"));
+    ASSERT_FALSE(auth->Authenticate("test", "456"));
+    ASSERT_TRUE(auth->RemoveUser(user->username()));
   }
 }
 
@@ -382,36 +363,53 @@ TEST_F(AuthWithStorage, PasswordStrength) {
   const std::string kAlmostStrongPassword = "ThisPasswordMeetsAllButOneCriterion1234";
   const std::string kStrongPassword = "ThisIsAVeryStrongPassword123$";
 
-  auto user = auth.AddUser("user");
-  ASSERT_TRUE(user);
+  {
+    auth.reset();
+    auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))),
+                 Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kWeakRegex, true});
+    auto user = auth->AddUser("user1");
+    ASSERT_TRUE(user);
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, std::nullopt));
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kWeakPassword));
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kAlmostStrongPassword));
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword));
+  }
 
-  FLAGS_auth_password_permit_null = true;
-  FLAGS_auth_password_strength_regex = kWeakRegex;
-  ASSERT_NO_THROW(user->UpdatePassword());
-  ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword));
-  ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword));
-  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
+  {
+    auth.reset();
+    auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))),
+                 Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kWeakRegex, false});
+    ASSERT_THROW(auth->AddUser("user2", std::nullopt), AuthException);
+    auto user = auth->AddUser("user2", kWeakPassword);
+    ASSERT_TRUE(user);
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kWeakPassword));
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kAlmostStrongPassword));
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword));
+  }
 
-  FLAGS_auth_password_permit_null = false;
-  FLAGS_auth_password_strength_regex = kWeakRegex;
-  ASSERT_THROW(user->UpdatePassword(), AuthException);
-  ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword));
-  ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword));
-  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
+  {
+    auth.reset();
+    auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))),
+                 Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kStrongRegex, true});
+    auto user = auth->AddUser("user3");
+    ASSERT_TRUE(user);
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, std::nullopt));
+    ASSERT_THROW(auth->UpdatePassword(*user, kWeakPassword), AuthException);
+    ASSERT_THROW(auth->UpdatePassword(*user, kAlmostStrongPassword), AuthException);
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword));
+  }
 
-  FLAGS_auth_password_permit_null = true;
-  FLAGS_auth_password_strength_regex = kStrongRegex;
-  ASSERT_NO_THROW(user->UpdatePassword());
-  ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException);
-  ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException);
-  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
-
-  FLAGS_auth_password_permit_null = false;
-  FLAGS_auth_password_strength_regex = kStrongRegex;
-  ASSERT_THROW(user->UpdatePassword(), AuthException);
-  ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException);
-  ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException);
-  ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
+  {
+    auth.reset();
+    auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))),
+                 Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kStrongRegex, false});
+    ASSERT_THROW(auth->AddUser("user4", std::nullopt);, AuthException);
+    ASSERT_THROW(auth->AddUser("user4", kWeakPassword);, AuthException);
+    ASSERT_THROW(auth->AddUser("user4", kAlmostStrongPassword);, AuthException);
+    auto user = auth->AddUser("user4", kStrongPassword);
+    ASSERT_TRUE(user);
+    ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword));
+  }
 }
 
 TEST(AuthWithoutStorage, Permissions) {
@@ -668,6 +666,17 @@ TEST(AuthWithoutStorage, UserSerializeDeserialize) {
   ASSERT_EQ(user, output);
 }
 
+TEST(AuthWithoutStorage, UserSerializeDeserializeWithOutPassword) {
+  auto user = User("test");
+  user.permissions().Grant(Permission::MATCH);
+  user.permissions().Deny(Permission::MERGE);
+
+  auto data = user.Serialize();
+
+  auto output = User::Deserialize(data);
+  ASSERT_EQ(user, output);
+}
+
 TEST(AuthWithoutStorage, RoleSerializeDeserialize) {
   auto role = Role("test");
   role.permissions().Grant(Permission::MATCH);
@@ -680,30 +689,30 @@ TEST(AuthWithoutStorage, RoleSerializeDeserialize) {
 }
 
 TEST_F(AuthWithStorage, UserWithRoleSerializeDeserialize) {
-  auto role = auth.AddRole("role");
+  auto role = auth->AddRole("role");
   ASSERT_TRUE(role);
   role->permissions().Grant(Permission::MATCH);
   role->permissions().Deny(Permission::MERGE);
-  auth.SaveRole(*role);
+  auth->SaveRole(*role);
 
-  auto user = auth.AddUser("user");
+  auto user = auth->AddUser("user");
   ASSERT_TRUE(user);
   user->permissions().Grant(Permission::MATCH);
   user->permissions().Deny(Permission::MERGE);
   user->UpdatePassword("world");
   user->SetRole(*role);
-  auth.SaveUser(*user);
+  auth->SaveUser(*user);
 
-  auto new_user = auth.GetUser("user");
+  auto new_user = auth->GetUser("user");
   ASSERT_TRUE(new_user);
   ASSERT_EQ(*user, *new_user);
 }
 
 TEST_F(AuthWithStorage, UserRoleUniqueName) {
-  ASSERT_TRUE(auth.AddUser("user"));
-  ASSERT_TRUE(auth.AddRole("role"));
-  ASSERT_FALSE(auth.AddRole("user"));
-  ASSERT_FALSE(auth.AddUser("role"));
+  ASSERT_TRUE(auth->AddUser("user"));
+  ASSERT_TRUE(auth->AddRole("role"));
+  ASSERT_FALSE(auth->AddRole("user"));
+  ASSERT_FALSE(auth->AddUser("role"));
 }
 
 TEST(AuthWithoutStorage, CaseInsensitivity) {
@@ -718,8 +727,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
   {
     auto perms = Permissions();
     auto fine_grained_access_handler = FineGrainedAccessHandler();
-    auto user1 = User("test", "pw", perms, fine_grained_access_handler);
-    auto user2 = User("Test", "pw", perms, fine_grained_access_handler);
+    auto passwordHash = HashPassword("pw");
+    auto user1 = User("test", passwordHash, perms, fine_grained_access_handler);
+    auto user2 = User("Test", passwordHash, perms, fine_grained_access_handler);
     ASSERT_EQ(user1, user2);
     ASSERT_EQ(user1.username(), user2.username());
     ASSERT_EQ(user1.username(), "test");
@@ -748,58 +758,58 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
 TEST_F(AuthWithStorage, CaseInsensitivity) {
   // AddUser
   {
-    auto user = auth.AddUser("Alice", "alice");
+    auto user = auth->AddUser("Alice", "alice");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
-    ASSERT_FALSE(auth.AddUser("alice"));
-    ASSERT_FALSE(auth.AddUser("alicE"));
+    ASSERT_FALSE(auth->AddUser("alice"));
+    ASSERT_FALSE(auth->AddUser("alicE"));
   }
   {
-    auto user = auth.AddUser("BoB", "bob");
+    auto user = auth->AddUser("BoB", "bob");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "bob");
-    ASSERT_FALSE(auth.AddUser("bob"));
-    ASSERT_FALSE(auth.AddUser("bOb"));
+    ASSERT_FALSE(auth->AddUser("bob"));
+    ASSERT_FALSE(auth->AddUser("bOb"));
   }
 
   // Authenticate
   {
-    auto user = auth.Authenticate("alice", "alice");
+    auto user = auth->Authenticate("alice", "alice");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
   }
   {
-    auto user = auth.Authenticate("alICe", "alice");
+    auto user = auth->Authenticate("alICe", "alice");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
   }
 
   // GetUser
   {
-    auto user = auth.GetUser("alice");
+    auto user = auth->GetUser("alice");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
   }
   {
-    auto user = auth.GetUser("aLicE");
+    auto user = auth->GetUser("aLicE");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
   }
-  ASSERT_FALSE(auth.GetUser("carol"));
+  ASSERT_FALSE(auth->GetUser("carol"));
 
   // RemoveUser
   {
-    auto user = auth.AddUser("caRol", "carol");
+    auto user = auth->AddUser("caRol", "carol");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "carol");
-    ASSERT_TRUE(auth.RemoveUser("cAROl"));
-    ASSERT_FALSE(auth.RemoveUser("carol"));
-    ASSERT_FALSE(auth.GetUser("CAROL"));
+    ASSERT_TRUE(auth->RemoveUser("cAROl"));
+    ASSERT_FALSE(auth->RemoveUser("carol"));
+    ASSERT_FALSE(auth->GetUser("CAROL"));
   }
 
   // AllUsers
   {
-    auto users = auth.AllUsers();
+    auto users = auth->AllUsers();
     ASSERT_EQ(users.size(), 2);
     std::sort(users.begin(), users.end(), [](const auto &a, const auto &b) { return a.username() < b.username(); });
     ASSERT_EQ(users[0].username(), "alice");
@@ -808,48 +818,48 @@ TEST_F(AuthWithStorage, CaseInsensitivity) {
 
   // AddRole
   {
-    auto role = auth.AddRole("Moderator");
+    auto role = auth->AddRole("Moderator");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "moderator");
-    ASSERT_FALSE(auth.AddRole("moderator"));
-    ASSERT_FALSE(auth.AddRole("MODERATOR"));
+    ASSERT_FALSE(auth->AddRole("moderator"));
+    ASSERT_FALSE(auth->AddRole("MODERATOR"));
   }
   {
-    auto role = auth.AddRole("adMIN");
+    auto role = auth->AddRole("adMIN");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "admin");
-    ASSERT_FALSE(auth.AddRole("Admin"));
-    ASSERT_FALSE(auth.AddRole("ADMIn"));
+    ASSERT_FALSE(auth->AddRole("Admin"));
+    ASSERT_FALSE(auth->AddRole("ADMIn"));
   }
-  ASSERT_FALSE(auth.AddRole("ALICE"));
-  ASSERT_FALSE(auth.AddUser("ModeRAtor"));
+  ASSERT_FALSE(auth->AddRole("ALICE"));
+  ASSERT_FALSE(auth->AddUser("ModeRAtor"));
 
   // GetRole
   {
-    auto role = auth.GetRole("moderator");
+    auto role = auth->GetRole("moderator");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "moderator");
   }
   {
-    auto role = auth.GetRole("MoDERATOR");
+    auto role = auth->GetRole("MoDERATOR");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "moderator");
   }
-  ASSERT_FALSE(auth.GetRole("root"));
+  ASSERT_FALSE(auth->GetRole("root"));
 
   // RemoveRole
   {
-    auto role = auth.AddRole("RooT");
+    auto role = auth->AddRole("RooT");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "root");
-    ASSERT_TRUE(auth.RemoveRole("rOOt"));
-    ASSERT_FALSE(auth.RemoveRole("RoOt"));
-    ASSERT_FALSE(auth.GetRole("RoOt"));
+    ASSERT_TRUE(auth->RemoveRole("rOOt"));
+    ASSERT_FALSE(auth->RemoveRole("RoOt"));
+    ASSERT_FALSE(auth->GetRole("RoOt"));
   }
 
   // AllRoles
   {
-    auto roles = auth.AllRoles();
+    auto roles = auth->AllRoles();
     ASSERT_EQ(roles.size(), 2);
     std::sort(roles.begin(), roles.end(), [](const auto &a, const auto &b) { return a.rolename() < b.rolename(); });
     ASSERT_EQ(roles[0].rolename(), "admin");
@@ -858,14 +868,14 @@ TEST_F(AuthWithStorage, CaseInsensitivity) {
 
   // SaveRole
   {
-    auto role = auth.GetRole("MODErator");
+    auto role = auth->GetRole("MODErator");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "moderator");
     role->permissions().Grant(memgraph::auth::Permission::MATCH);
-    auth.SaveRole(*role);
+    auth->SaveRole(*role);
   }
   {
-    auto role = auth.GetRole("modeRATOR");
+    auto role = auth->GetRole("modeRATOR");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "moderator");
     ASSERT_EQ(role->permissions().Has(memgraph::auth::Permission::MATCH), memgraph::auth::PermissionLevel::GRANT);
@@ -873,17 +883,17 @@ TEST_F(AuthWithStorage, CaseInsensitivity) {
 
   // SaveUser
   {
-    auto user = auth.GetUser("aLice");
+    auto user = auth->GetUser("aLice");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
-    auto role = auth.GetRole("moderAtor");
+    auto role = auth->GetRole("moderAtor");
     ASSERT_TRUE(role);
     ASSERT_EQ(role->rolename(), "moderator");
     user->SetRole(*role);
-    auth.SaveUser(*user);
+    auth->SaveUser(*user);
   }
   {
-    auto user = auth.GetUser("aLIce");
+    auto user = auth->GetUser("aLIce");
     ASSERT_TRUE(user);
     ASSERT_EQ(user->username(), "alice");
     const auto *role = user->role();
@@ -893,27 +903,27 @@ TEST_F(AuthWithStorage, CaseInsensitivity) {
 
   // AllUsersForRole
   {
-    auto carol = auth.AddUser("caROl");
+    auto carol = auth->AddUser("caROl");
     ASSERT_TRUE(carol);
     ASSERT_EQ(carol->username(), "carol");
-    auto dave = auth.AddUser("daVe");
+    auto dave = auth->AddUser("daVe");
     ASSERT_TRUE(dave);
     ASSERT_EQ(dave->username(), "dave");
-    auto admin = auth.GetRole("aDMin");
+    auto admin = auth->GetRole("aDMin");
     ASSERT_TRUE(admin);
     ASSERT_EQ(admin->rolename(), "admin");
     carol->SetRole(*admin);
-    auth.SaveUser(*carol);
+    auth->SaveUser(*carol);
     dave->SetRole(*admin);
-    auth.SaveUser(*dave);
+    auth->SaveUser(*dave);
   }
   {
-    auto users = auth.AllUsersForRole("modeRAtoR");
+    auto users = auth->AllUsersForRole("modeRAtoR");
     ASSERT_EQ(users.size(), 1);
     ASSERT_EQ(users[0].username(), "alice");
   }
   {
-    auto users = auth.AllUsersForRole("AdmiN");
+    auto users = auth->AllUsersForRole("AdmiN");
     ASSERT_EQ(users.size(), 2);
     std::sort(users.begin(), users.end(), [](const auto &a, const auto &b) { return a.username() < b.username(); });
     ASSERT_EQ(users[0].username(), "carol");
@@ -922,53 +932,49 @@ TEST_F(AuthWithStorage, CaseInsensitivity) {
 }
 
 TEST(AuthWithoutStorage, Crypto) {
-  auto hash = EncryptPassword("hello");
-  ASSERT_TRUE(VerifyPassword("hello", hash));
-  ASSERT_FALSE(VerifyPassword("hello1", hash));
+  auto hash = HashPassword("hello");
+  ASSERT_TRUE(hash.VerifyPassword("hello"));
+  ASSERT_FALSE(hash.VerifyPassword("hello1"));
 }
 
 class AuthWithVariousEncryptionAlgorithms : public ::testing::Test {
  protected:
-  void SetUp() override { FLAGS_password_encryption_algorithm = "bcrypt"; }
+  void SetUp() override { SetHashAlgorithm("bcrypt"); }
 };
 
 TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordDefault) {
-  auto hash = EncryptPassword("hello");
-  ASSERT_TRUE(VerifyPassword("hello", hash));
-  ASSERT_FALSE(VerifyPassword("hello1", hash));
+  auto hash = HashPassword("hello");
+  ASSERT_TRUE(hash.VerifyPassword("hello"));
+  ASSERT_FALSE(hash.VerifyPassword("hello1"));
 }
 
 TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordSHA256) {
-  FLAGS_password_encryption_algorithm = "sha256";
-  auto hash = EncryptPassword("hello");
-  ASSERT_TRUE(VerifyPassword("hello", hash));
-  ASSERT_FALSE(VerifyPassword("hello1", hash));
+  SetHashAlgorithm("sha256");
+  auto hash = HashPassword("hello");
+  ASSERT_TRUE(hash.VerifyPassword("hello"));
+  ASSERT_FALSE(hash.VerifyPassword("hello1"));
 }
 
 TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordSHA256_1024) {
-  FLAGS_password_encryption_algorithm = "sha256-multiple";
-  auto hash = EncryptPassword("hello");
-  ASSERT_TRUE(VerifyPassword("hello", hash));
-  ASSERT_FALSE(VerifyPassword("hello1", hash));
+  SetHashAlgorithm("sha256-multiple");
+  auto hash = HashPassword("hello");
+  ASSERT_TRUE(hash.VerifyPassword("hello"));
+  ASSERT_FALSE(hash.VerifyPassword("hello1"));
 }
 
-TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordThrow) {
-  FLAGS_password_encryption_algorithm = "abcd";
-  ASSERT_THROW(EncryptPassword("hello"), AuthException);
+TEST_F(AuthWithVariousEncryptionAlgorithms, SetEncryptionAlgorithmNonsenseThrow) {
+  ASSERT_THROW(SetHashAlgorithm("abcd"), AuthException);
 }
 
-TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordEmptyEncryptionThrow) {
-  FLAGS_password_encryption_algorithm = "";
-  ASSERT_THROW(EncryptPassword("hello"), AuthException);
+TEST_F(AuthWithVariousEncryptionAlgorithms, SetEncryptionAlgorithmEmptyThrow) {
+  ASSERT_THROW(SetHashAlgorithm(""), AuthException);
 }
 
 class AuthWithStorageWithVariousEncryptionAlgorithms : public ::testing::Test {
  protected:
   void SetUp() override {
     memgraph::utils::EnsureDir(test_folder_);
-    FLAGS_auth_password_permit_null = true;
-    FLAGS_auth_password_strength_regex = ".+";
-    FLAGS_password_encryption_algorithm = "bcrypt";
+    SetHashAlgorithm("bcrypt");
 
     memgraph::license::global_license_checker.EnableTesting();
   }
@@ -976,8 +982,8 @@ class AuthWithStorageWithVariousEncryptionAlgorithms : public ::testing::Test {
   void TearDown() override { fs::remove_all(test_folder_); }
 
   fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"};
-
-  Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))};
+  Auth::Config auth_config{};
+  Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), auth_config};
 };
 
 TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserDefault) {
@@ -987,25 +993,26 @@ TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserDefault) {
 }
 
 TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserSha256) {
-  FLAGS_password_encryption_algorithm = "sha256";
+  SetHashAlgorithm("sha256");
   auto user = auth.AddUser("Alice", "alice");
   ASSERT_TRUE(user);
   ASSERT_EQ(user->username(), "alice");
 }
 
 TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserSha256_1024) {
-  FLAGS_password_encryption_algorithm = "sha256-multiple";
+  SetHashAlgorithm("sha256-multiple");
   auto user = auth.AddUser("Alice", "alice");
   ASSERT_TRUE(user);
   ASSERT_EQ(user->username(), "alice");
 }
 
-TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserThrow) {
-  FLAGS_password_encryption_algorithm = "abcd";
-  ASSERT_THROW(auth.AddUser("Alice", "alice"), AuthException);
-}
-
-TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserEmptyPasswordEncryptionThrow) {
-  FLAGS_password_encryption_algorithm = "";
-  ASSERT_THROW(auth.AddUser("Alice", "alice"), AuthException);
+TEST(Serialize, HashedPassword) {
+  for (auto algo :
+       {PasswordHashAlgorithm::BCRYPT, PasswordHashAlgorithm::SHA256, PasswordHashAlgorithm::SHA256_MULTIPLE}) {
+    auto sut = HashPassword("password", algo);
+    nlohmann::json j = sut;
+    auto ret = j.get<HashedPassword>();
+    ASSERT_EQ(sut, ret);
+    ASSERT_TRUE(ret.VerifyPassword("password"));
+  }
 }
diff --git a/tests/unit/auth_checker.cpp b/tests/unit/auth_checker.cpp
index b048e9491..f4c499cd7 100644
--- a/tests/unit/auth_checker.cpp
+++ b/tests/unit/auth_checker.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,7 +22,7 @@
 #include "storage/v2/disk/storage.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/view.hpp"
-
+using memgraph::replication_coordination_glue::ReplicationRole;
 #ifdef MG_ENTERPRISE
 template <typename StorageType>
 class FineGrainedAuthCheckerFixture : public testing::Test {
@@ -31,7 +31,7 @@ class FineGrainedAuthCheckerFixture : public testing::Test {
 
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
 
   // make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2)
diff --git a/tests/unit/auth_handler.cpp b/tests/unit/auth_handler.cpp
index 6537575fd..a162d1838 100644
--- a/tests/unit/auth_handler.cpp
+++ b/tests/unit/auth_handler.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -26,8 +26,9 @@ class AuthQueryHandlerFixture : public testing::Test {
  protected:
   std::filesystem::path test_folder_{std::filesystem::temp_directory_path() / "MG_tests_unit_auth_handler"};
   memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth{
-      test_folder_ / ("unit_auth_handler_test_" + std::to_string(static_cast<int>(getpid())))};
-  memgraph::glue::AuthQueryHandler auth_handler{&auth, memgraph::glue::kDefaultUserRoleRegex.data()};
+      test_folder_ / ("unit_auth_handler_test_" + std::to_string(static_cast<int>(getpid()))),
+      memgraph::auth::Auth::Config{/* default */}};
+  memgraph::glue::AuthQueryHandler auth_handler{&auth};
 
   std::string user_name = "Mate";
   std::string edge_type_repr = "EdgeType1";
@@ -56,7 +57,7 @@ TEST_F(AuthQueryHandlerFixture, GivenAuthQueryHandlerWhenInitializedHaveNoUserna
 }
 
 TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturned) {
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   auth->SaveUser(user);
 
   { ASSERT_EQ(auth_handler.GetUsernames().size(), 1); }
@@ -70,7 +71,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturn
 
 TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturned) {
   perms.Grant(memgraph::auth::Permission::MATCH);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -91,7 +92,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturne
 
 TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned) {
   perms.Deny(memgraph::auth::Permission::MATCH);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -113,7 +114,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned
 TEST_F(AuthQueryHandlerFixture, GivenUserWhenPrivilegeRevokedThenNothingIsReturned) {
   perms.Deny(memgraph::auth::Permission::MATCH);
   perms.Revoke(memgraph::auth::Permission::MATCH);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -179,7 +180,7 @@ TEST_F(AuthQueryHandlerFixture, GivenRoleWhenPrivilegeRevokedThenNothingIsReturn
 TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedTwoPrivilegesThenBothAreReturned) {
   perms.Grant(memgraph::auth::Permission::MATCH);
   perms.Grant(memgraph::auth::Permission::CREATE);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -190,7 +191,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherGrantedThe
   perms.Grant(memgraph::auth::Permission::MATCH);
   memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms};
   auth->SaveRole(role);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   user.SetRole(role);
   auth->SaveUser(user);
 
@@ -214,7 +215,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherDeniedThenB
   perms.Deny(memgraph::auth::Permission::MATCH);
   memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms};
   auth->SaveRole(role);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms};
   user.SetRole(role);
   auth->SaveUser(user);
 
@@ -244,7 +245,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherDeniedThen
   user_perms.Grant(memgraph::auth::Permission::MATCH);
   memgraph::auth::User user = memgraph::auth::User{
       user_name,
-      "",
+      std::nullopt,
       user_perms,
   };
   user.SetRole(role);
@@ -274,7 +275,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherGrantedThen
 
   memgraph::auth::Permissions user_perms{};
   user_perms.Deny(memgraph::auth::Permission::MATCH);
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", user_perms};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, user_perms};
   user.SetRole(role);
   auth->SaveUser(user);
 
@@ -304,7 +305,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnLabelThenIsDispla
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -333,7 +334,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnLabelThe
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -363,7 +364,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnLabelThenTopO
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -391,7 +392,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnLabelThenIs
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -420,7 +421,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnLa
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -450,7 +451,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnLabelTh
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -479,7 +480,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnEdgeTypeThenIsDis
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -508,7 +509,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnEdgeType
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -538,7 +539,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnEdgeTypeThenT
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -566,7 +567,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnEdgeTypeThe
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -596,7 +597,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnEd
 
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -626,7 +627,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnEdgeTyp
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -655,7 +656,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnLabelThenNoPermis
       memgraph::auth::FineGrainedAccessPermissions{},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -684,7 +685,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnEdgeTypeThenNoPer
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
@@ -713,7 +714,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedReadAndDeniedUpdateThenOneIs
       memgraph::auth::FineGrainedAccessPermissions{read_permission},
   };
 
-  memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler};
+  memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler};
   auth->SaveUser(user);
 
   auto privileges = auth_handler.GetPrivileges(user_name);
diff --git a/tests/unit/bfs_fine_grained.cpp b/tests/unit/bfs_fine_grained.cpp
index 235431804..568206dfd 100644
--- a/tests/unit/bfs_fine_grained.cpp
+++ b/tests/unit/bfs_fine_grained.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -42,7 +42,9 @@ class VertexDb : public Database {
     }
   }
 
-  std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { return db_->Access(); }
+  std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override {
+    return db_->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
+  }
 
   std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym,
                                                    EdgeAtom::Direction direction,
diff --git a/tests/unit/bfs_single_node.cpp b/tests/unit/bfs_single_node.cpp
index 37aae0491..a6816242d 100644
--- a/tests/unit/bfs_single_node.cpp
+++ b/tests/unit/bfs_single_node.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -31,7 +31,9 @@ class SingleNodeDb : public Database {
     }
   }
 
-  std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { return db_->Access(); }
+  std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override {
+    return db_->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
+  }
 
   std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym,
                                                    EdgeAtom::Direction direction,
diff --git a/tests/unit/bolt_encoder.cpp b/tests/unit/bolt_encoder.cpp
index 6ffb4b496..19a958118 100644
--- a/tests/unit/bolt_encoder.cpp
+++ b/tests/unit/bolt_encoder.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -182,7 +182,7 @@ void TestVertexAndEdgeWithDifferentStorages(std::unique_ptr<memgraph::storage::S
   output.clear();
 
   // create vertex
-  auto dba = db->Access();
+  auto dba = db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
   auto va1 = dba->CreateVertex();
   auto va2 = dba->CreateVertex();
   auto l1 = dba->NameToLabel("label1");
@@ -206,11 +206,11 @@ void TestVertexAndEdgeWithDifferentStorages(std::unique_ptr<memgraph::storage::S
 
   // check everything
   std::vector<Value> vals;
-  vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)), *db,
-                                              memgraph::storage::View::NEW));
-  vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)), *db,
-                                              memgraph::storage::View::NEW));
-  vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(ea)), *db,
+  vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)),
+                                              db.get(), memgraph::storage::View::NEW));
+  vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)),
+                                              db.get(), memgraph::storage::View::NEW));
+  vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(ea)), db.get(),
                                               memgraph::storage::View::NEW));
   bolt_encoder.MessageRecord(vals);
 
diff --git a/tests/unit/clearing_old_disk_data.cpp b/tests/unit/clearing_old_disk_data.cpp
index 92b0de134..395391e12 100644
--- a/tests/unit/clearing_old_disk_data.cpp
+++ b/tests/unit/clearing_old_disk_data.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -20,6 +20,8 @@
 #include "storage/v2/property_value.hpp"
 #include "storage/v2/view.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 class ClearingOldDiskDataTest : public ::testing::Test {
  public:
   const std::string testSuite = "clearing_old_disk_data";
@@ -33,7 +35,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexTimestampUpdate) {
   auto *tx_db = disk_storage->GetRocksDBStorage()->db_;
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0);
 
-  auto acc1 = disk_storage->Access(std::nullopt);
+  auto acc1 = disk_storage->Access(ReplicationRole::MAIN);
   auto vertex1 = acc1->CreateVertex();
   auto label1 = acc1->NameToLabel("DiskLabel");
   auto property1 = acc1->NameToProperty("DiskProperty");
@@ -43,7 +45,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexTimestampUpdate) {
 
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-  auto acc2 = disk_storage->Access(std::nullopt);
+  auto acc2 = disk_storage->Access(ReplicationRole::MAIN);
   auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value();
   /// This is the same property as in the first transaction, we just want to test
   /// the number of entries inside RocksDB when the timestamp changes
@@ -58,7 +60,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexValueUpdate) {
   auto *tx_db = disk_storage->GetRocksDBStorage()->db_;
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0);
 
-  auto acc1 = disk_storage->Access(std::nullopt);
+  auto acc1 = disk_storage->Access(ReplicationRole::MAIN);
   auto vertex1 = acc1->CreateVertex();
   auto label1 = acc1->NameToLabel("DiskLabel");
   auto property1 = acc1->NameToProperty("DiskProperty");
@@ -68,7 +70,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexValueUpdate) {
 
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-  auto acc2 = disk_storage->Access(std::nullopt);
+  auto acc2 = disk_storage->Access(ReplicationRole::MAIN);
   auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value();
   /// This is the same property as in the first transaction, we just want to test
   /// the number of entries inside RocksDB when the timestamp changes
@@ -83,7 +85,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexKeyUpdate) {
   auto *tx_db = disk_storage->GetRocksDBStorage()->db_;
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0);
 
-  auto acc1 = disk_storage->Access(std::nullopt);
+  auto acc1 = disk_storage->Access(ReplicationRole::MAIN);
   auto vertex1 = acc1->CreateVertex();
   auto label1 = acc1->NameToLabel("DiskLabel");
   auto property1 = acc1->NameToProperty("DiskProperty");
@@ -93,7 +95,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexKeyUpdate) {
 
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-  auto acc2 = disk_storage->Access(std::nullopt);
+  auto acc2 = disk_storage->Access(ReplicationRole::MAIN);
   auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value();
   auto label2 = acc2->NameToLabel("DiskLabel2");
   ASSERT_TRUE(vertex2.AddLabel(label2).HasValue());
@@ -106,7 +108,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeTimestampUpdate) {
   auto *tx_db = disk_storage->GetRocksDBStorage()->db_;
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0);
 
-  auto acc1 = disk_storage->Access(std::nullopt);
+  auto acc1 = disk_storage->Access(ReplicationRole::MAIN);
 
   auto label1 = acc1->NameToLabel("DiskLabel");
   auto property1 = acc1->NameToProperty("DiskProperty");
@@ -126,7 +128,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeTimestampUpdate) {
 
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 5);
 
-  auto acc2 = disk_storage->Access(std::nullopt);
+  auto acc2 = disk_storage->Access(ReplicationRole::MAIN);
   auto from_vertex = acc2->FindVertex(from.Gid(), memgraph::storage::View::NEW).value();
 
   auto ret = from_vertex.OutEdges(memgraph::storage::View::NEW);
@@ -145,7 +147,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeValueUpdate) {
   auto *tx_db = disk_storage->GetRocksDBStorage()->db_;
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0);
 
-  auto acc1 = disk_storage->Access(std::nullopt);
+  auto acc1 = disk_storage->Access(ReplicationRole::MAIN);
 
   auto label1 = acc1->NameToLabel("DiskLabel");
   auto property1 = acc1->NameToProperty("DiskProperty");
@@ -165,7 +167,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeValueUpdate) {
 
   ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 5);
 
-  auto acc2 = disk_storage->Access(std::nullopt);
+  auto acc2 = disk_storage->Access(ReplicationRole::MAIN);
   auto from_vertex = acc2->FindVertex(from.Gid(), memgraph::storage::View::NEW).value();
 
   auto ret = from_vertex.OutEdges(memgraph::storage::View::NEW);
diff --git a/tests/unit/cpp_api.cpp b/tests/unit/cpp_api.cpp
index 012b2d713..84ca0b195 100644
--- a/tests/unit/cpp_api.cpp
+++ b/tests/unit/cpp_api.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -43,7 +43,8 @@ struct CppApiTestFixture : public ::testing::Test {
   }
 
   memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) {
-    accessors_.push_back(storage->Access(isolationLevel));
+    accessors_.push_back(
+        storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN, isolationLevel));
     db_accessors_.emplace_back(accessors_.back().get());
     return db_accessors_.back();
   }
diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp
index 31fd95c6c..1353a56dd 100644
--- a/tests/unit/cypher_main_visitor.cpp
+++ b/tests/unit/cypher_main_visitor.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -37,11 +37,13 @@
 #include "query/frontend/ast/cypher_main_visitor.hpp"
 #include "query/frontend/opencypher/parser.hpp"
 #include "query/frontend/stripped.hpp"
+#include "query/parameters.hpp"
 #include "query/procedure/cypher_types.hpp"
 #include "query/procedure/mg_procedure_impl.hpp"
 #include "query/procedure/module.hpp"
 #include "query/typed_value.hpp"
 
+#include "utils/logging.hpp"
 #include "utils/string.hpp"
 #include "utils/variant_helpers.hpp"
 
@@ -118,7 +120,8 @@ class AstGenerator : public Base {
  public:
   Query *ParseQuery(const std::string &query_string) override {
     ::frontend::opencypher::Parser parser(query_string);
-    CypherMainVisitor visitor(context_, &ast_storage_);
+    Parameters parameters;
+    CypherMainVisitor visitor(context_, &ast_storage_, &parameters);
     visitor.visit(parser.tree());
     return visitor.query();
   }
@@ -151,6 +154,7 @@ class ClonedAstGenerator : public Base {
  public:
   Query *ParseQuery(const std::string &query_string) override {
     ::frontend::opencypher::Parser parser(query_string);
+    Parameters parameters;
     AstStorage tmp_storage;
     {
       // Add a label, property and edge type into temporary storage so
@@ -159,7 +163,7 @@ class ClonedAstGenerator : public Base {
       tmp_storage.GetPropertyIx("fdjakfjdklfjdaslk");
       tmp_storage.GetEdgeTypeIx("fdjkalfjdlkajfdkla");
     }
-    CypherMainVisitor visitor(context_, &tmp_storage);
+    CypherMainVisitor visitor(context_, &tmp_storage, &parameters);
     visitor.visit(parser.tree());
     return visitor.query()->Clone(&ast_storage_);
   }
@@ -182,8 +186,9 @@ class CachedAstGenerator : public Base {
     StrippedQuery stripped(query_string);
     parameters_ = stripped.literals();
     ::frontend::opencypher::Parser parser(stripped.query());
+    Parameters parameters;
     AstStorage tmp_storage;
-    CypherMainVisitor visitor(context_, &tmp_storage);
+    CypherMainVisitor visitor(context_, &tmp_storage, &parameters);
     visitor.visit(parser.tree());
     return visitor.query()->Clone(&ast_storage_);
   }
@@ -2546,7 +2551,7 @@ TEST_P(CypherMainVisitorTest, ShowUsersForRole) {
 void check_replication_query(Base *ast_generator, const ReplicationQuery *query, const std::string name,
                              const std::optional<TypedValue> socket_address, const ReplicationQuery::SyncMode sync_mode,
                              const std::optional<TypedValue> port = {}) {
-  EXPECT_EQ(query->replica_name_, name);
+  EXPECT_EQ(query->instance_name_, name);
   EXPECT_EQ(query->sync_mode_, sync_mode);
   ASSERT_EQ(static_cast<bool>(query->socket_address_), static_cast<bool>(socket_address));
   if (socket_address) {
@@ -2593,7 +2598,7 @@ TEST_P(CypherMainVisitorTest, TestSetReplicationMode) {
   }
 
   {
-    const std::string query = "SET REPLICATION ROLE TO MAIN WITH PORT 10000";
+    const std::string query = "SET REPLICATION ROLE TO REPLICA";
     ASSERT_THROW(ast_generator.ParseQuery(query), SemanticException);
   }
 
@@ -2636,7 +2641,7 @@ TEST_P(CypherMainVisitorTest, TestDeleteReplica) {
   std::string correct_query = "DROP REPLICA replica1";
   auto *correct_query_parsed = dynamic_cast<ReplicationQuery *>(ast_generator.ParseQuery(correct_query));
   ASSERT_TRUE(correct_query_parsed);
-  EXPECT_EQ(correct_query_parsed->replica_name_, "replica1");
+  EXPECT_EQ(correct_query_parsed->instance_name_, "replica1");
 }
 
 TEST_P(CypherMainVisitorTest, TestExplainRegularQuery) {
@@ -3641,7 +3646,7 @@ TEST_P(CypherMainVisitorTest, MemoryLimit) {
     ASSERT_TRUE(query->single_query_);
     auto *single_query = query->single_query_;
     ASSERT_EQ(single_query->clauses_.size(), 2U);
-    auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]);
+    [[maybe_unused]] auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]);
   }
 
   {
@@ -3701,7 +3706,7 @@ TEST_P(CypherMainVisitorTest, MemoryLimit) {
     ASSERT_TRUE(query->single_query_);
     auto *single_query = query->single_query_;
     ASSERT_EQ(single_query->clauses_.size(), 1U);
-    auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]);
+    [[maybe_unused]] auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]);
   }
 }
 
diff --git a/tests/unit/database_get_info.cpp b/tests/unit/database_get_info.cpp
index aac38159d..a8a275a61 100644
--- a/tests/unit/database_get_info.cpp
+++ b/tests/unit/database_get_info.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -93,12 +93,12 @@ TYPED_TEST(InfoTest, InfoCheck) {
 
   {
     {
-      auto unique_acc = db_acc->storage()->UniqueAccess();
+      auto unique_acc = db_acc->UniqueAccess();
       ASSERT_FALSE(unique_acc->CreateExistenceConstraint(lbl, prop).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
-      auto unique_acc = db_acc->storage()->UniqueAccess();
+      auto unique_acc = db_acc->UniqueAccess();
       ASSERT_FALSE(unique_acc->DropExistenceConstraint(lbl, prop).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
@@ -108,7 +108,7 @@ TYPED_TEST(InfoTest, InfoCheck) {
     auto v2 = acc->CreateVertex();
     auto v3 = acc->CreateVertex();
     auto v4 = acc->CreateVertex();
-    auto v5 = acc->CreateVertex();
+    [[maybe_unused]] auto v5 = acc->CreateVertex();
 
     ASSERT_FALSE(v2.AddLabel(lbl).HasError());
     ASSERT_FALSE(v3.AddLabel(lbl).HasError());
@@ -123,49 +123,50 @@ TYPED_TEST(InfoTest, InfoCheck) {
   }
 
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateIndex(lbl).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop2).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->DropIndex(lbl, prop).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl, {prop2}).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl2, {prop}).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl3, {prop}).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = db_acc->storage()->UniqueAccess();
+    auto unique_acc = db_acc->UniqueAccess();
     ASSERT_EQ(unique_acc->DropUniqueConstraint(lbl, {prop2}),
               memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  const auto &info = db_acc->GetInfo(true);  // force to use configured directory
+  const auto &info = db_acc->GetInfo(
+      true, memgraph::replication_coordination_glue::ReplicationRole::MAIN);  // force to use configured directory
 
   ASSERT_EQ(info.storage_info.vertex_count, 5);
   ASSERT_EQ(info.storage_info.edge_count, 2);
diff --git a/tests/unit/dbms_database.cpp b/tests/unit/dbms_database.cpp
index 20e1f55ac..535c0c055 100644
--- a/tests/unit/dbms_database.cpp
+++ b/tests/unit/dbms_database.cpp
@@ -29,7 +29,8 @@ memgraph::storage::Config default_conf(std::string name = "") {
   return {.durability = {.storage_directory = storage_directory / name,
                          .snapshot_wal_mode =
                              memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
-          .disk = {.main_storage_directory = storage_directory / name / "disk"}};
+          .disk = {.main_storage_directory = storage_directory / name / "disk"},
+          .salient.name = name.empty() ? std::string{"memgraph"} : name};
 }
 
 class DBMS_Database : public ::testing::Test {
@@ -55,20 +56,21 @@ TEST_F(DBMS_Database, New) {
         .durability = {.storage_directory = storage_directory / "db2",
                        .snapshot_wal_mode =
                            memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
-        .disk = {.main_storage_directory = storage_directory / "disk"}};
-    auto db2 = db_handler.New("db2", db_config, generic_repl_state);
+        .disk = {.main_storage_directory = storage_directory / "disk"},
+        .salient.name = "db2"};
+    auto db2 = db_handler.New(db_config, generic_repl_state);
     ASSERT_TRUE(db2.HasValue() && db2.GetValue());
     ASSERT_TRUE(std::filesystem::exists(storage_directory / "db2"));
   }
   {
     // With default config
-    auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state);
+    auto db3 = db_handler.New(default_conf("db3"), generic_repl_state);
     ASSERT_TRUE(db3.HasValue() && db3.GetValue());
     ASSERT_TRUE(std::filesystem::exists(storage_directory / "db3"));
-    auto db4 = db_handler.New("db4", default_conf("four"), generic_repl_state);
+    auto db4 = db_handler.New(default_conf("four"), generic_repl_state);
     ASSERT_TRUE(db4.HasValue() && db4.GetValue());
     ASSERT_TRUE(std::filesystem::exists(storage_directory / "four"));
-    auto db5 = db_handler.New("db5", default_conf("db3"), generic_repl_state);
+    auto db5 = db_handler.New(default_conf("db3"), generic_repl_state);
     ASSERT_TRUE(db5.HasError() && db5.GetError() == memgraph::dbms::NewError::EXISTS);
   }
 
@@ -77,15 +79,15 @@ TEST_F(DBMS_Database, New) {
   ASSERT_EQ(all.size(), 3);
   ASSERT_EQ(all[0], "db2");
   ASSERT_EQ(all[1], "db3");
-  ASSERT_EQ(all[2], "db4");
+  ASSERT_EQ(all[2], "four");
 }
 
 TEST_F(DBMS_Database, Get) {
   memgraph::dbms::DatabaseHandler db_handler;
 
-  auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
-  auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
-  auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state);
+  auto db1 = db_handler.New(default_conf("db1"), generic_repl_state);
+  auto db2 = db_handler.New(default_conf("db2"), generic_repl_state);
+  auto db3 = db_handler.New(default_conf("db3"), generic_repl_state);
 
   ASSERT_TRUE(db1.HasValue());
   ASSERT_TRUE(db2.HasValue());
@@ -107,9 +109,9 @@ TEST_F(DBMS_Database, Get) {
 TEST_F(DBMS_Database, Delete) {
   memgraph::dbms::DatabaseHandler db_handler;
 
-  auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
-  auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
-  auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state);
+  auto db1 = db_handler.New(default_conf("db1"), generic_repl_state);
+  auto db2 = db_handler.New(default_conf("db2"), generic_repl_state);
+  auto db3 = db_handler.New(default_conf("db3"), generic_repl_state);
 
   ASSERT_TRUE(db1.HasValue());
   ASSERT_TRUE(db2.HasValue());
@@ -119,7 +121,7 @@ TEST_F(DBMS_Database, Delete) {
     // Release accessor to storage
     db1.GetValue().reset();
     // Delete from handler
-    ASSERT_TRUE(db_handler.Delete("db1"));
+    ASSERT_TRUE(db_handler.TryDelete("db1"));
     ASSERT_FALSE(db_handler.Get("db1"));
     auto all = db_handler.All();
     std::sort(all.begin(), all.end());
@@ -129,8 +131,8 @@ TEST_F(DBMS_Database, Delete) {
   }
 
   {
-    ASSERT_THROW(db_handler.Delete("db0"), memgraph::utils::BasicException);
-    ASSERT_THROW(db_handler.Delete("db1"), memgraph::utils::BasicException);
+    ASSERT_THROW(db_handler.TryDelete("db0"), memgraph::utils::BasicException);
+    ASSERT_THROW(db_handler.TryDelete("db1"), memgraph::utils::BasicException);
     auto all = db_handler.All();
     std::sort(all.begin(), all.end());
     ASSERT_EQ(all.size(), 2);
@@ -144,17 +146,18 @@ TEST_F(DBMS_Database, DeleteAndRecover) {
   memgraph::dbms::DatabaseHandler db_handler;
 
   {
-    auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
-    auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
+    auto db1 = db_handler.New(default_conf("db1"), generic_repl_state);
+    auto db2 = db_handler.New(default_conf("db2"), generic_repl_state);
 
     memgraph::storage::Config conf_w_snap{
         .durability = {.storage_directory = storage_directory / "db3",
                        .snapshot_wal_mode =
                            memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
                        .snapshot_on_exit = true},
-        .disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
+        .disk = {.main_storage_directory = storage_directory / "db3" / "disk"},
+        .salient.name = "db3"};
 
-    auto db3 = db_handler.New("db3", conf_w_snap, generic_repl_state);
+    auto db3 = db_handler.New(conf_w_snap, generic_repl_state);
 
     ASSERT_TRUE(db1.HasValue());
     ASSERT_TRUE(db2.HasValue());
@@ -184,23 +187,24 @@ TEST_F(DBMS_Database, DeleteAndRecover) {
   }
 
   // Delete from handler
-  ASSERT_TRUE(db_handler.Delete("db1"));
-  ASSERT_TRUE(db_handler.Delete("db2"));
-  ASSERT_TRUE(db_handler.Delete("db3"));
+  ASSERT_TRUE(db_handler.TryDelete("db1"));
+  ASSERT_TRUE(db_handler.TryDelete("db2"));
+  ASSERT_TRUE(db_handler.TryDelete("db3"));
 
   {
     // Recover graphs (only db3)
-    auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
-    auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
+    auto db1 = db_handler.New(default_conf("db1"), generic_repl_state);
+    auto db2 = db_handler.New(default_conf("db2"), generic_repl_state);
 
     memgraph::storage::Config conf_w_rec{
         .durability = {.storage_directory = storage_directory / "db3",
                        .recover_on_startup = true,
                        .snapshot_wal_mode =
                            memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
-        .disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
+        .disk = {.main_storage_directory = storage_directory / "db3" / "disk"},
+        .salient.name = "db3"};
 
-    auto db3 = db_handler.New("db3", conf_w_rec, generic_repl_state);
+    auto db3 = db_handler.New(conf_w_rec, generic_repl_state);
 
     // Check content
     {
diff --git a/tests/unit/dbms_handler.cpp b/tests/unit/dbms_handler.cpp
index 0ea4197fb..2abe0b77d 100644
--- a/tests/unit/dbms_handler.cpp
+++ b/tests/unit/dbms_handler.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -25,8 +25,23 @@
 #include "query/config.hpp"
 #include "query/interpreter.hpp"
 
+namespace {
+std::set<std::string> GetDirs(auto path) {
+  std::set<std::string> dirs;
+  // Clean the unused directories
+  for (const auto &entry : std::filesystem::directory_iterator(path)) {
+    const auto &name = entry.path().filename().string();
+    if (entry.is_directory() && !name.empty() && name.front() != '.') {
+      dirs.emplace(name);
+    }
+  }
+  return dirs;
+}
+}  // namespace
+
 // Global
 std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_handler"};
+std::filesystem::path db_dir{storage_directory / "databases"};
 static memgraph::storage::Config storage_conf;
 std::unique_ptr<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>> auth;
 
@@ -51,8 +66,8 @@ class TestEnvironment : public ::testing::Environment {
     }
     auth =
         std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>(
-            storage_directory / "auth");
-    ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, auth.get(), false, true);
+            storage_directory / "auth", memgraph::auth::Auth::Config{/* default */});
+    ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, auth.get(), false);
   }
 
   void TearDown() override {
@@ -74,7 +89,7 @@ TEST(DBMS_Handler, Init) {
   std::vector<std::string> dirs = {"snapshots", "streams", "triggers", "wal"};
   for (const auto &dir : dirs)
     ASSERT_TRUE(std::filesystem::exists(storage_directory / dir)) << (storage_directory / dir);
-  const auto db_path = storage_directory / "databases" / memgraph::dbms::kDefaultDB;
+  const auto db_path = db_dir / memgraph::dbms::kDefaultDB;
   ASSERT_TRUE(std::filesystem::exists(db_path));
   for (const auto &dir : dirs) {
     std::error_code ec;
@@ -92,10 +107,14 @@ TEST(DBMS_Handler, New) {
     ASSERT_EQ(all[0], memgraph::dbms::kDefaultDB);
   }
   {
+    const auto dirs = GetDirs(db_dir);
     auto db1 = dbms.New("db1");
     ASSERT_TRUE(db1.HasValue());
     ASSERT_TRUE(db1.GetValue());
-    ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "db1"));
+    // New flow doesn't make db named directories
+    ASSERT_FALSE(std::filesystem::exists(db_dir / "db1"));
+    const auto dirs_w_db1 = GetDirs(db_dir);
+    ASSERT_EQ(dirs_w_db1.size(), dirs.size() + 1);
     ASSERT_TRUE(db1.GetValue()->storage() != nullptr);
     ASSERT_TRUE(db1.GetValue()->streams() != nullptr);
     ASSERT_TRUE(db1.GetValue()->trigger_store() != nullptr);
@@ -111,9 +130,13 @@ TEST(DBMS_Handler, New) {
     ASSERT_TRUE(db2.HasError() && db2.GetError() == memgraph::dbms::NewError::EXISTS);
   }
   {
+    const auto dirs = GetDirs(db_dir);
     auto db3 = dbms.New("db3");
     ASSERT_TRUE(db3.HasValue());
-    ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "db3"));
+    // New flow doesn't make db named directories
+    ASSERT_FALSE(std::filesystem::exists(db_dir / "db3"));
+    const auto dirs_w_db3 = GetDirs(db_dir);
+    ASSERT_EQ(dirs_w_db3.size(), dirs.size() + 1);
     ASSERT_TRUE(db3.GetValue()->storage() != nullptr);
     ASSERT_TRUE(db3.GetValue()->streams() != nullptr);
     ASSERT_TRUE(db3.GetValue()->trigger_store() != nullptr);
@@ -156,16 +179,16 @@ TEST(DBMS_Handler, Delete) {
   auto db1_acc = dbms.Get("db1");  // Holds access to database
 
   {
-    auto del = dbms.Delete(memgraph::dbms::kDefaultDB);
+    auto del = dbms.TryDelete(memgraph::dbms::kDefaultDB);
     ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::DEFAULT_DB);
   }
   {
-    auto del = dbms.Delete("non-existent");
+    auto del = dbms.TryDelete("non-existent");
     ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT);
   }
   {
     // db1_acc is using db1
-    auto del = dbms.Delete("db1");
+    auto del = dbms.TryDelete("db1");
     ASSERT_TRUE(del.HasError());
     ASSERT_TRUE(del.GetError() == memgraph::dbms::DeleteError::USING);
   }
@@ -173,15 +196,17 @@ TEST(DBMS_Handler, Delete) {
     // Reset db1_acc (releases access) so delete will succeed
     db1_acc.reset();
     ASSERT_FALSE(db1_acc);
-    auto del = dbms.Delete("db1");
+    auto del = dbms.TryDelete("db1");
     ASSERT_FALSE(del.HasError()) << (int)del.GetError();
-    auto del2 = dbms.Delete("db1");
+    auto del2 = dbms.TryDelete("db1");
     ASSERT_TRUE(del2.HasError() && del2.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT);
   }
   {
-    auto del = dbms.Delete("db3");
+    const auto dirs = GetDirs(db_dir);
+    auto del = dbms.TryDelete("db3");
     ASSERT_FALSE(del.HasError());
-    ASSERT_FALSE(std::filesystem::exists(storage_directory / "databases" / "db3"));
+    const auto dirs_wo_db3 = GetDirs(db_dir);
+    ASSERT_EQ(dirs_wo_db3.size(), dirs.size() - 1);
   }
 }
 
diff --git a/tests/unit/dbms_handler_community.cpp b/tests/unit/dbms_handler_community.cpp
index 860f70ba0..4a47e018b 100644
--- a/tests/unit/dbms_handler_community.cpp
+++ b/tests/unit/dbms_handler_community.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -51,7 +51,7 @@ class TestEnvironment : public ::testing::Environment {
     }
     auth =
         std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>(
-            storage_directory / "auth");
+            storage_directory / "auth", memgraph::auth::Auth::Config{/* default */});
     ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf);
   }
 
@@ -90,9 +90,9 @@ TEST(DBMS_Handler, Get) {
   ASSERT_TRUE(default_db->streams() != nullptr);
   ASSERT_TRUE(default_db->trigger_store() != nullptr);
   ASSERT_TRUE(default_db->thread_pool() != nullptr);
-  ASSERT_EQ(default_db->storage()->id(), memgraph::dbms::kDefaultDB);
+  ASSERT_EQ(default_db->storage()->name(), memgraph::dbms::kDefaultDB);
   auto conf = storage_conf;
-  conf.name = memgraph::dbms::kDefaultDB;
+  conf.salient.name = memgraph::dbms::kDefaultDB;
   ASSERT_EQ(default_db->storage()->config_, conf);
 }
 
diff --git a/tests/unit/interpreter_faker.hpp b/tests/unit/interpreter_faker.hpp
index 5823c6a87..3b6075911 100644
--- a/tests/unit/interpreter_faker.hpp
+++ b/tests/unit/interpreter_faker.hpp
@@ -21,8 +21,9 @@ struct InterpreterFaker {
   }
 
   auto Prepare(const std::string &query, const std::map<std::string, memgraph::storage::PropertyValue> &params = {}) {
-    ResultStreamFaker stream(interpreter.current_db_.db_acc_->get()->storage());
     const auto [header, _1, qid, _2] = interpreter.Prepare(query, params, {});
+    auto &db = interpreter.current_db_.db_acc_;
+    ResultStreamFaker stream(db ? db->get()->storage() : nullptr);
     stream.Header(header);
     return std::make_pair(std::move(stream), qid);
   }
diff --git a/tests/unit/multi_tenancy.cpp b/tests/unit/multi_tenancy.cpp
new file mode 100644
index 000000000..59364776a
--- /dev/null
+++ b/tests/unit/multi_tenancy.cpp
@@ -0,0 +1,377 @@
+// Copyright 2024 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#include <algorithm>
+#include <cstdlib>
+#include <filesystem>
+#include <thread>
+
+#include "communication/bolt/v1/value.hpp"
+#include "communication/result_stream_faker.hpp"
+#include "csv/parsing.hpp"
+#include "dbms/dbms_handler.hpp"
+#include "disk_test_utils.hpp"
+#include "flags/run_time_configurable.hpp"
+#include "glue/communication.hpp"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "interpreter_faker.hpp"
+#include "license/license.hpp"
+#include "query/auth_checker.hpp"
+#include "query/config.hpp"
+#include "query/exceptions.hpp"
+#include "query/interpreter.hpp"
+#include "query/interpreter_context.hpp"
+#include "query/metadata.hpp"
+#include "query/stream.hpp"
+#include "query/typed_value.hpp"
+#include "query_common.hpp"
+#include "replication/state.hpp"
+#include "storage/v2/inmemory/storage.hpp"
+#include "storage/v2/isolation_level.hpp"
+#include "storage/v2/property_value.hpp"
+#include "storage/v2/storage_mode.hpp"
+#include "utils/logging.hpp"
+#include "utils/lru_cache.hpp"
+#include "utils/synchronized.hpp"
+
+namespace {
+std::set<std::string> GetDirs(auto path) {
+  std::set<std::string> dirs;
+  // Clean the unused directories
+  for (const auto &entry : std::filesystem::directory_iterator(path)) {
+    const auto &name = entry.path().filename().string();
+    if (entry.is_directory() && !name.empty() && name.front() != '.') {
+      dirs.emplace(name);
+    }
+  }
+  return dirs;
+}
+
+auto RunMtQuery(auto &interpreter, const std::string &query, std::string_view res) {
+  auto [stream, qid] = interpreter.Prepare(query);
+  ASSERT_EQ(stream.GetHeader().size(), 1U);
+  EXPECT_EQ(stream.GetHeader()[0], "STATUS");
+  interpreter.Pull(&stream, 1);
+  ASSERT_EQ(stream.GetSummary().count("has_more"), 1);
+  ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool());
+  ASSERT_EQ(stream.GetResults()[0].size(), 1U);
+  ASSERT_EQ(stream.GetResults()[0][0].ValueString(), res);
+}
+
+auto RunQuery(auto &interpreter, const std::string &query) {
+  auto [stream, qid] = interpreter.Prepare(query);
+  interpreter.Pull(&stream, 1);
+  return stream.GetResults();
+}
+
+void UseDatabase(auto &interpreter, const std::string &name, std::string_view res) {
+  RunMtQuery(interpreter, "USE DATABASE " + name, res);
+}
+
+void DropDatabase(auto &interpreter, const std::string &name, std::string_view res) {
+  RunMtQuery(interpreter, "DROP DATABASE " + name, res);
+}
+}  // namespace
+
+class MultiTenantTest : public ::testing::Test {
+ public:
+  std::filesystem::path data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_multi_tenancy";
+
+  MultiTenantTest() = default;
+
+  memgraph::storage::Config config{
+      [&]() {
+        memgraph::storage::Config config{};
+        UpdatePaths(config, data_directory);
+        return config;
+      }()  // iile
+  };
+
+  struct MinMemgraph {
+    explicit MinMemgraph(const memgraph::storage::Config &conf)
+        : auth{conf.durability.storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}},
+          dbms{conf, &auth, true},
+          interpreter_context{{}, &dbms, &dbms.ReplicationState()} {
+      memgraph::utils::global_settings.Initialize(conf.durability.storage_directory / "settings");
+      memgraph::license::RegisterLicenseSettings(memgraph::license::global_license_checker,
+                                                 memgraph::utils::global_settings);
+      memgraph::flags::run_time::Initialize();
+      memgraph::license::global_license_checker.CheckEnvLicense();
+    }
+
+    ~MinMemgraph() { memgraph::utils::global_settings.Finalize(); }
+
+    auto NewInterpreter() { return InterpreterFaker{&interpreter_context, dbms.Get()}; }
+
+    memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth;
+    memgraph::dbms::DbmsHandler dbms;
+    memgraph::query::InterpreterContext interpreter_context;
+  };
+
+  void SetUp() override {
+    TearDown();
+    min_mg.emplace(config);
+  }
+
+  void TearDown() override {
+    min_mg.reset();
+    if (std::filesystem::exists(data_directory)) std::filesystem::remove_all(data_directory);
+  }
+
+  auto NewInterpreter() { return min_mg->NewInterpreter(); }
+
+  auto &DBMS() { return min_mg->dbms; }
+
+  std::optional<MinMemgraph> min_mg;
+};
+
+TEST_F(MultiTenantTest, SimpleCreateDrop) {
+  // 1) Create multiple interpreters with the default db
+  // 2) Create multiple databases using both
+  // 3) Drop databases while the other is using
+
+  // 1
+  auto interpreter1 = this->NewInterpreter();
+  auto interpreter2 = this->NewInterpreter();
+
+  // 2
+  auto create = [&](auto &interpreter, const std::string &name, bool success) {
+    RunMtQuery(interpreter, "CREATE DATABASE " + name,
+               success ? ("Successfully created database " + name) : (name + " already exists."));
+  };
+
+  create(interpreter1, "db1", true);
+  create(interpreter1, "db1", false);
+  create(interpreter2, "db1", false);
+  create(interpreter2, "db2", true);
+  create(interpreter1, "db2", false);
+  create(interpreter2, "db3", true);
+  create(interpreter2, "db4", true);
+
+  // 3
+  UseDatabase(interpreter1, "db2", "Using db2");
+  UseDatabase(interpreter1, "db2", "Already using db2");
+  UseDatabase(interpreter2, "db2", "Using db2");
+  UseDatabase(interpreter1, "db4", "Using db4");
+
+  ASSERT_THROW(DropDatabase(interpreter1, memgraph::dbms::kDefaultDB.data(), ""),
+               memgraph::query::QueryRuntimeException);  // default db
+
+  DropDatabase(interpreter1, "db1", "Successfully deleted db1");
+  ASSERT_THROW(DropDatabase(interpreter2, "db1", ""), memgraph::query::QueryRuntimeException);  // No db1
+  ASSERT_THROW(DropDatabase(interpreter1, "db1", ""), memgraph::query::QueryRuntimeException);  // No db1
+
+  ASSERT_THROW(DropDatabase(interpreter1, "db2", ""), memgraph::query::QueryRuntimeException);  // i2 using db2
+  ASSERT_THROW(DropDatabase(interpreter1, "db4", ""), memgraph::query::QueryRuntimeException);  // i1 using db4
+}
+
+TEST_F(MultiTenantTest, DbmsNewTryDelete) {
+  // 1) Create multiple interpreters with the default db
+  // 2) Create multiple databases using dbms
+  // 3) Try delete databases while the interpreters are using them
+
+  // 1
+  auto interpreter1 = this->NewInterpreter();
+  auto interpreter2 = this->NewInterpreter();
+
+  // 2
+  auto &dbms = DBMS();
+  ASSERT_FALSE(dbms.New("db1").HasError());
+  ASSERT_FALSE(dbms.New("db2").HasError());
+  ASSERT_FALSE(dbms.New("db3").HasError());
+  ASSERT_FALSE(dbms.New("db4").HasError());
+
+  // 3
+  UseDatabase(interpreter2, "db2", "Using db2");
+  UseDatabase(interpreter1, "db4", "Using db4");
+
+  ASSERT_FALSE(dbms.TryDelete("db1").HasError());
+  ASSERT_TRUE(dbms.TryDelete("db2").HasError());
+  ASSERT_FALSE(dbms.TryDelete("db3").HasError());
+  ASSERT_TRUE(dbms.TryDelete("db4").HasError());
+}
+
+TEST_F(MultiTenantTest, DbmsUpdate) {
+  // 1) Create multiple interpreters with the default db
+  // 2) Create multiple databases using dbms
+  // 3) Try to update databases
+
+  auto &dbms = DBMS();
+  auto interpreter1 = this->NewInterpreter();
+
+  // Update clean default db
+  auto default_db = dbms.Get();
+  const auto old_uuid = default_db->config().salient.uuid;
+  const memgraph::utils::UUID new_uuid{/* random */};
+  const memgraph::storage::SalientConfig &config{.name = "memgraph", .uuid = new_uuid};
+  auto new_default = dbms.Update(config);
+  ASSERT_TRUE(new_default.HasValue());
+  ASSERT_NE(new_uuid, old_uuid);
+  ASSERT_EQ(default_db->storage(), new_default.GetValue()->storage());
+
+  // Add node to default
+  RunQuery(interpreter1, "CREATE (:Node)");
+
+  // Fail to update dirty default db
+  const memgraph::storage::SalientConfig &failing_config{.name = "memgraph", .uuid = {}};
+  auto failed_update = dbms.Update(failing_config);
+  ASSERT_TRUE(failed_update.HasError());
+
+  // Succeed when updating with the same config
+  auto same_update = dbms.Update(config);
+  ASSERT_TRUE(same_update.HasValue());
+  ASSERT_EQ(new_default.GetValue()->storage(), same_update.GetValue()->storage());
+
+  // Create new db
+  auto db1 = dbms.New("db1");
+  ASSERT_FALSE(db1.HasError());
+  RunMtQuery(interpreter1, "USE DATABASE db1", "Using db1");
+  RunQuery(interpreter1, "CREATE (:NewNode)");
+  RunQuery(interpreter1, "CREATE (:NewNode)");
+  const auto db1_config_old = db1.GetValue()->config();
+
+  // Begin a transaction on db1
+  auto interpreter2 = this->NewInterpreter();
+  RunMtQuery(interpreter2, "USE DATABASE db1", "Using db1");
+  ASSERT_EQ(RunQuery(interpreter2, "SHOW DATABASE")[0][0].ValueString(), "db1");
+  RunQuery(interpreter2, "BEGIN");
+
+  // Update and check the new db in clean
+  auto interpreter3 = this->NewInterpreter();
+  const memgraph::storage::SalientConfig &db1_config_new{.name = "db1", .uuid = {}};
+  auto new_db1 = dbms.Update(db1_config_new);
+  ASSERT_TRUE(new_db1.HasValue());
+  ASSERT_NE(db1_config_new.uuid, db1_config_old.salient.uuid);
+  RunMtQuery(interpreter3, "USE DATABASE db1", "Using db1");
+  ASSERT_EQ(RunQuery(interpreter3, "MATCH(n) RETURN count(*)")[0][0].ValueInt(), 0);
+
+  // Check that the interpreter1 is still valid, but lacking a db
+  ASSERT_THROW(RunQuery(interpreter1, "CREATE (:Node)"), memgraph::query::DatabaseContextRequiredException);
+
+  // Check that the interpreter2 is still valid and pointing to the old db1 (until commit)
+  RunQuery(interpreter2, "CREATE (:NewNode)");
+  ASSERT_EQ(RunQuery(interpreter2, "MATCH(n) RETURN count(*)")[0][0].ValueInt(), 3);
+  RunQuery(interpreter2, "COMMIT");
+  ASSERT_THROW(RunQuery(interpreter2, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException);
+}
+
+TEST_F(MultiTenantTest, DbmsNewDelete) {
+  // 1) Create multiple interpreters with the default db
+  // 2) Create multiple databases using dbms
+  // 3) Defer delete databases while the interpreters are using them
+  // 4) Database should be a zombie until the using interpreter retries to query it
+  // 5) Check it is deleted from disk
+
+  // 1
+  auto interpreter1 = this->NewInterpreter();
+  auto interpreter2 = this->NewInterpreter();
+
+  // 2
+  auto &dbms = DBMS();
+  ASSERT_FALSE(dbms.New("db1").HasError());
+  ASSERT_FALSE(dbms.New("db2").HasError());
+  ASSERT_FALSE(dbms.New("db3").HasError());
+  ASSERT_FALSE(dbms.New("db4").HasError());
+
+  // 3
+  UseDatabase(interpreter2, "db2", "Using db2");
+  UseDatabase(interpreter1, "db4", "Using db4");
+
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})");
+  RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})");
+
+  ASSERT_FALSE(dbms.Delete("db1").HasError());
+  ASSERT_FALSE(dbms.Delete("db2").HasError());
+  ASSERT_FALSE(dbms.Delete("db3").HasError());
+  ASSERT_FALSE(dbms.Delete("db4").HasError());
+
+  // 4
+  ASSERT_EQ(dbms.All().size(), 1);
+  ASSERT_EQ(GetDirs(data_directory / "databases").size(), 3);  // All used databases remain on disk, but unusable
+  ASSERT_THROW(RunQuery(interpreter1, "MATCH(:Node{on:db4}) RETURN count(*)"),
+               memgraph::query::DatabaseContextRequiredException);
+  ASSERT_THROW(RunQuery(interpreter2, "MATCH(:Node{on:db2}) RETURN count(*)"),
+               memgraph::query::DatabaseContextRequiredException);
+
+  // 5
+  using namespace std::chrono_literals;
+  std::this_thread::sleep_for(100ms);                          // Wait for the filesystem to be updated
+  ASSERT_EQ(GetDirs(data_directory / "databases").size(), 1);  // Databases deleted from disk
+  ASSERT_THROW(RunQuery(interpreter1, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException);
+  ASSERT_THROW(RunQuery(interpreter2, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException);
+}
+
+TEST_F(MultiTenantTest, DbmsNewDeleteWTx) {
+  // 1) Create multiple interpreters with the default db
+  // 2) Create multiple databases using dbms
+  // 3) Defer delete databases while the interpreters are using them
+  // 4) Interpreters that had an open transaction before should still be working
+  // 5) New transactions on deleted databases should throw
+  // 6) Switching databases should still be possible
+
+  // 1
+  auto interpreter1 = this->NewInterpreter();
+  auto interpreter2 = this->NewInterpreter();
+
+  // 2
+  auto &dbms = DBMS();
+  ASSERT_FALSE(dbms.New("db1").HasError());
+  ASSERT_FALSE(dbms.New("db2").HasError());
+  ASSERT_FALSE(dbms.New("db3").HasError());
+  ASSERT_FALSE(dbms.New("db4").HasError());
+
+  // 3
+  UseDatabase(interpreter2, "db2", "Using db2");
+  UseDatabase(interpreter1, "db4", "Using db4");
+
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})");
+  RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})");
+  RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})");
+
+  RunQuery(interpreter1, "BEGIN");
+  RunQuery(interpreter2, "BEGIN");
+
+  ASSERT_FALSE(dbms.Delete("db1").HasError());
+  ASSERT_FALSE(dbms.Delete("db2").HasError());
+  ASSERT_FALSE(dbms.Delete("db3").HasError());
+  ASSERT_FALSE(dbms.Delete("db4").HasError());
+
+  // 4
+  ASSERT_EQ(dbms.All().size(), 1);
+  ASSERT_EQ(GetDirs(data_directory / "databases").size(), 3);  // All used databases remain on disk, and usable
+  ASSERT_EQ(RunQuery(interpreter1, "MATCH(:Node{on:\"db4\"}) RETURN count(*)")[0][0].ValueInt(), 4);
+  ASSERT_EQ(RunQuery(interpreter2, "MATCH(:Node{on:\"db2\"}) RETURN count(*)")[0][0].ValueInt(), 2);
+  RunQuery(interpreter1, "MATCH(n:Node{on:\"db4\"}) DELETE n");
+  RunQuery(interpreter2, "CREATE(:Node{on:\"db2\"})");
+  ASSERT_EQ(RunQuery(interpreter1, "MATCH(:Node{on:\"db4\"}) RETURN count(*)")[0][0].ValueInt(), 0);
+  ASSERT_EQ(RunQuery(interpreter2, "MATCH(:Node{on:\"db2\"}) RETURN count(*)")[0][0].ValueInt(), 3);
+  RunQuery(interpreter1, "COMMIT");
+  RunQuery(interpreter2, "COMMIT");
+
+  // 5
+  using namespace std::chrono_literals;
+  std::this_thread::sleep_for(100ms);                          // Wait for the filesystem to be updated
+  ASSERT_EQ(GetDirs(data_directory / "databases").size(), 1);  // Only the active databases remain
+  ASSERT_THROW(RunQuery(interpreter1, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException);
+  ASSERT_THROW(RunQuery(interpreter2, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException);
+
+  // 6
+  UseDatabase(interpreter2, memgraph::dbms::kDefaultDB.data(), "Using memgraph");
+  UseDatabase(interpreter1, memgraph::dbms::kDefaultDB.data(), "Using memgraph");
+}
diff --git a/tests/unit/plan_pretty_print.cpp b/tests/unit/plan_pretty_print.cpp
index 97f1355cb..ef2395931 100644
--- a/tests/unit/plan_pretty_print.cpp
+++ b/tests/unit/plan_pretty_print.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -43,7 +43,7 @@ class PrintToJsonTest : public ::testing::Test {
   PrintToJsonTest()
       : config(disk_test_utils::GenerateOnDiskConfig(testSuite)),
         db(new StorageType(config)),
-        dba_storage(db->Access()),
+        dba_storage(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)),
         dba(dba_storage.get()) {}
 
   ~PrintToJsonTest() override {
diff --git a/tests/unit/query_cost_estimator.cpp b/tests/unit/query_cost_estimator.cpp
index f3d6b3864..702b6e759 100644
--- a/tests/unit/query_cost_estimator.cpp
+++ b/tests/unit/query_cost_estimator.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,12 +17,13 @@
 #include "query/frontend/semantic/symbol_table.hpp"
 #include "query/plan/cost_estimator.hpp"
 #include "query/plan/operator.hpp"
+#include "query/plan/rewrite/index_lookup.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/storage.hpp"
 
 using namespace memgraph::query;
 using namespace memgraph::query::plan;
-
+using memgraph::replication_coordination_glue::ReplicationRole;
 using CardParam = CostEstimator<memgraph::query::DbAccessor>::CardParam;
 using CostParam = CostEstimator<memgraph::query::DbAccessor>::CostParam;
 using MiscParam = CostEstimator<memgraph::query::DbAccessor>::MiscParam;
@@ -51,16 +52,16 @@ class QueryCostEstimator : public ::testing::Test {
 
   void SetUp() override {
     {
-      auto unique_acc = db->UniqueAccess();
+      auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateIndex(label).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
-      auto unique_acc = db->UniqueAccess();
+      auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateIndex(label, property).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
-    storage_dba.emplace(db->Access());
+    storage_dba.emplace(db->Access(ReplicationRole::MAIN));
     dba.emplace(storage_dba->get());
   }
 
@@ -83,7 +84,8 @@ class QueryCostEstimator : public ::testing::Test {
   }
 
   auto Cost() {
-    CostEstimator<memgraph::query::DbAccessor> cost_estimator(&*dba, symbol_table_, parameters_);
+    CostEstimator<memgraph::query::DbAccessor> cost_estimator(&*dba, symbol_table_, parameters_,
+                                                              memgraph::query::plan::IndexHints());
     last_op_->Accept(cost_estimator);
     return cost_estimator.cost();
   }
diff --git a/tests/unit/query_dump.cpp b/tests/unit/query_dump.cpp
index 63019ad28..23eab17e0 100644
--- a/tests/unit/query_dump.cpp
+++ b/tests/unit/query_dump.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -9,10 +9,12 @@
 // by the Apache License, Version 2.0, included in the file
 // licenses/APL.txt.
 
+#include <gtest/gtest-typed-test.h>
 #include <gtest/gtest.h>
 
 #include <filesystem>
 #include <map>
+#include <optional>
 #include <set>
 #include <vector>
 
@@ -24,6 +26,8 @@
 #include "query/interpreter.hpp"
 #include "query/interpreter_context.hpp"
 #include "query/stream/streams.hpp"
+#include "query/trigger.hpp"
+#include "query/trigger_context.hpp"
 #include "query/typed_value.hpp"
 #include "storage/v2/config.hpp"
 #include "storage/v2/disk/storage.hpp"
@@ -137,7 +141,7 @@ DatabaseState GetState(memgraph::storage::Storage *db) {
   // Capture all vertices
   std::map<memgraph::storage::Gid, int64_t> gid_mapping;
   std::set<DatabaseState::Vertex> vertices;
-  auto dba = db->Access();
+  auto dba = db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
   for (const auto &vertex : dba->Vertices(memgraph::storage::View::NEW)) {
     std::set<std::string, std::less<>> labels;
     auto maybe_labels = vertex.Labels(memgraph::storage::View::NEW);
@@ -263,7 +267,7 @@ memgraph::storage::EdgeAccessor CreateEdge(memgraph::storage::Storage::Accessor
 }
 
 template <class... TArgs>
-void VerifyQueries(const std::vector<std::vector<memgraph::communication::bolt::Value>> &results, TArgs &&...args) {
+void VerifyQueries(const std::vector<std::vector<memgraph::communication::bolt::Value>> &results, TArgs &&... args) {
   std::vector<std::string> expected{std::forward<TArgs>(args)...};
   std::vector<std::string> got;
   got.reserve(results.size());
@@ -329,9 +333,9 @@ TYPED_TEST(DumpTest, EmptyGraph) {
   ResultStreamFaker stream(this->db->storage());
   memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
   {
-    auto acc = this->db->storage()->Access();
+    auto acc = this->db->Access();
     memgraph::query::DbAccessor dba(acc.get());
-    memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+    memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
   }
   ASSERT_EQ(stream.GetResults().size(), 0);
 }
@@ -339,7 +343,7 @@ TYPED_TEST(DumpTest, EmptyGraph) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, SingleVertex) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {}, {}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -348,9 +352,9 @@ TYPED_TEST(DumpTest, SingleVertex) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   kDropInternalIndex, kRemoveInternalLabelProperty);
@@ -360,7 +364,7 @@ TYPED_TEST(DumpTest, SingleVertex) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, VertexWithSingleLabel) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {"Label1"}, {}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -369,9 +373,9 @@ TYPED_TEST(DumpTest, VertexWithSingleLabel) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1` {__mg_id__: 0});",
                   kDropInternalIndex, kRemoveInternalLabelProperty);
@@ -381,7 +385,7 @@ TYPED_TEST(DumpTest, VertexWithSingleLabel) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, VertexWithMultipleLabels) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {"Label1", "Label 2"}, {}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -390,9 +394,9 @@ TYPED_TEST(DumpTest, VertexWithMultipleLabels) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex,
                   "CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0});", kDropInternalIndex,
@@ -403,7 +407,7 @@ TYPED_TEST(DumpTest, VertexWithMultipleLabels) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, VertexWithSingleProperty) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {}, {{"prop", memgraph::storage::PropertyValue(42)}}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -412,9 +416,9 @@ TYPED_TEST(DumpTest, VertexWithSingleProperty) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0, `prop`: 42});",
                   kDropInternalIndex, kRemoveInternalLabelProperty);
@@ -424,7 +428,7 @@ TYPED_TEST(DumpTest, VertexWithSingleProperty) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, MultipleVertices) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {}, {}, false);
     CreateVertex(dba.get(), {}, {}, false);
     CreateVertex(dba.get(), {}, {}, false);
@@ -435,9 +439,9 @@ TYPED_TEST(DumpTest, MultipleVertices) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});",
@@ -447,7 +451,7 @@ TYPED_TEST(DumpTest, MultipleVertices) {
 
 TYPED_TEST(DumpTest, PropertyValue) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     auto null_value = memgraph::storage::PropertyValue();
     auto int_value = memgraph::storage::PropertyValue(13);
     auto bool_value = memgraph::storage::PropertyValue(true);
@@ -473,9 +477,9 @@ TYPED_TEST(DumpTest, PropertyValue) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex,
                   "CREATE (:__mg_vertex__ {__mg_id__: 0, `p1`: [{`prop 1`: 13, "
@@ -489,7 +493,7 @@ TYPED_TEST(DumpTest, PropertyValue) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, SingleEdge) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     auto u = CreateVertex(dba.get(), {}, {}, false);
     auto v = CreateVertex(dba.get(), {}, {}, false);
     CreateEdge(dba.get(), &u, &v, "EdgeType", {}, false);
@@ -500,9 +504,9 @@ TYPED_TEST(DumpTest, SingleEdge) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});",
@@ -515,7 +519,7 @@ TYPED_TEST(DumpTest, SingleEdge) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, MultipleEdges) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     auto u = CreateVertex(dba.get(), {}, {}, false);
     auto v = CreateVertex(dba.get(), {}, {}, false);
     auto w = CreateVertex(dba.get(), {}, {}, false);
@@ -529,9 +533,9 @@ TYPED_TEST(DumpTest, MultipleEdges) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});",
@@ -548,7 +552,7 @@ TYPED_TEST(DumpTest, MultipleEdges) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, EdgeWithProperties) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     auto u = CreateVertex(dba.get(), {}, {}, false);
     auto v = CreateVertex(dba.get(), {}, {}, false);
     CreateEdge(dba.get(), &u, &v, "EdgeType", {{"prop", memgraph::storage::PropertyValue(13)}}, false);
@@ -559,9 +563,9 @@ TYPED_TEST(DumpTest, EdgeWithProperties) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});",
                   "CREATE (:__mg_vertex__ {__mg_id__: 1});",
@@ -574,7 +578,7 @@ TYPED_TEST(DumpTest, EdgeWithProperties) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, IndicesKeys) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {"Label1", "Label 2"}, {{"p", memgraph::storage::PropertyValue(1)}}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -599,9 +603,9 @@ TYPED_TEST(DumpTest, IndicesKeys) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), "CREATE INDEX ON :`Label1`(`prop`);", "CREATE INDEX ON :`Label 2`(`prop ```);",
                   kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0, `p`: 1});",
@@ -612,7 +616,7 @@ TYPED_TEST(DumpTest, IndicesKeys) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, ExistenceConstraints) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {"L`abel 1"}, {{"prop", memgraph::storage::PropertyValue(1)}}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -628,9 +632,9 @@ TYPED_TEST(DumpTest, ExistenceConstraints) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(), "CREATE CONSTRAINT ON (u:`L``abel 1`) ASSERT EXISTS (u.`prop`);",
                   kCreateInternalIndex, "CREATE (:__mg_vertex__:`L``abel 1` {__mg_id__: 0, `prop`: 1});",
@@ -640,7 +644,7 @@ TYPED_TEST(DumpTest, ExistenceConstraints) {
 
 TYPED_TEST(DumpTest, UniqueConstraints) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {"Label"},
                  {{"prop", memgraph::storage::PropertyValue(1)}, {"prop2", memgraph::storage::PropertyValue(2)}},
                  false);
@@ -663,9 +667,9 @@ TYPED_TEST(DumpTest, UniqueConstraints) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     VerifyQueries(stream.GetResults(),
                   "CREATE CONSTRAINT ON (u:`Label`) ASSERT u.`prop`, u.`prop2` "
@@ -682,7 +686,7 @@ TYPED_TEST(DumpTest, UniqueConstraints) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     std::map<std::string, memgraph::storage::PropertyValue> prop1 = {
         {"nested1", memgraph::storage::PropertyValue(1337)}, {"nested2", memgraph::storage::PropertyValue(3.14)}};
 
@@ -700,11 +704,13 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
     config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s1").disk;
     config.force_on_disk = true;
   }
-  auto on_exit_s1 = memgraph::utils::OnScopeExit{[&]() {
-    if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) {
+  auto clean_up_s1 = memgraph::utils::OnScopeExit{[&] {
+    if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
       disk_test_utils::RemoveRocksDbDirs("query-dump-s1");
     }
+    std::filesystem::remove_all(config.durability.storage_directory);
   }};
+
   memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(config));
 
   memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config, repl_state);
@@ -722,9 +728,9 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     const auto &results = stream.GetResults();
     ASSERT_GE(results.size(), 1);
@@ -739,7 +745,7 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     auto u = CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Ivan")}});
     auto v = CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Josko")}});
     auto w = CreateVertex(
@@ -819,11 +825,13 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
     config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s2").disk;
     config.force_on_disk = true;
   }
-  auto on_exit_s2 = memgraph::utils::OnScopeExit{[&]() {
-    if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) {
+  auto clean_up_s2 = memgraph::utils::OnScopeExit{[&] {
+    if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) {
       disk_test_utils::RemoveRocksDbDirs("query-dump-s2");
     }
+    std::filesystem::remove_all(config.durability.storage_directory);
   }};
+
   memgraph::replication::ReplicationState repl_state{ReplicationStateRootPath(config)};
   memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
   auto db_acc_opt = db_gk.access();
@@ -839,9 +847,9 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
     ResultStreamFaker stream(this->db->storage());
     memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
     {
-      auto acc = this->db->storage()->Access();
+      auto acc = this->db->Access();
       memgraph::query::DbAccessor dba(acc.get());
-      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
+      memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db);
     }
     const auto &results = stream.GetResults();
     // Indices and constraints are 4 queries and there must be at least one more
@@ -862,7 +870,7 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(DumpTest, ExecuteDumpDatabase) {
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {}, {}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -925,7 +933,7 @@ TYPED_TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) {
 
   // Create the vertex.
   {
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     CreateVertex(dba.get(), {}, {}, false);
     ASSERT_FALSE(dba->Commit().HasError());
   }
@@ -1023,7 +1031,7 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
 
-    auto dba = this->db->storage()->Access();
+    auto dba = this->db->Access();
     auto p1 = CreateVertex(dba.get(), {"PERSON"},
                            {{"name", memgraph::storage::PropertyValue("Person1")},
                             {"surname", memgraph::storage::PropertyValue("Unique1")}},
@@ -1053,10 +1061,10 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
 
   ResultStreamFaker stream(this->db->storage());
   memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
-  auto acc = this->db->storage()->Access();
+  auto acc = this->db->Access();
   memgraph::query::DbAccessor dba(acc.get());
 
-  memgraph::query::PullPlanDump pullPlan{&dba};
+  memgraph::query::PullPlanDump pullPlan{&dba, this->db};
 
   auto offset_index = 0U;
   auto check_next = [&](const std::string &expected_row) mutable {
@@ -1095,3 +1103,30 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
   check_next(kDropInternalIndex);
   check_next(kRemoveInternalLabelProperty);
 }
+
+TYPED_TEST(DumpTest, DumpDatabaseWithTriggers) {
+  auto acc = this->db->storage()->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
+  memgraph::query::DbAccessor dba(acc.get());
+  {
+    auto trigger_store = this->db.get()->trigger_store();
+    const std::string trigger_name = "test_trigger";
+    const std::string trigger_statement = "UNWIND createdVertices AS newNodes SET newNodes.created = timestamp()";
+    memgraph::query::TriggerEventType trigger_event_type = memgraph::query::TriggerEventType::VERTEX_CREATE;
+    memgraph::query::TriggerPhase trigger_phase = memgraph::query::TriggerPhase::AFTER_COMMIT;
+    memgraph::utils::SkipList<memgraph::query::QueryCacheEntry> ast_cache;
+    memgraph::query::AllowEverythingAuthChecker auth_checker;
+    memgraph::query::InterpreterConfig::Query query_config;
+    memgraph::query::DbAccessor dba(acc.get());
+    const std::map<std::string, memgraph::storage::PropertyValue> props;
+    trigger_store->AddTrigger(trigger_name, trigger_statement, props, trigger_event_type, trigger_phase, &ast_cache,
+                              &dba, query_config, std::nullopt, &auth_checker);
+  }
+  {
+    ResultStreamFaker stream(this->db->storage());
+    memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
+    { memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); }
+    VerifyQueries(stream.GetResults(),
+                  "CREATE TRIGGER test_trigger ON () CREATE AFTER COMMIT EXECUTE UNWIND createdVertices AS newNodes "
+                  "SET newNodes.created = timestamp();");
+  }
+}
diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp
index c9786fe5e..c725d7e54 100644
--- a/tests/unit/query_expression_evaluator.cpp
+++ b/tests/unit/query_expression_evaluator.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -67,7 +67,7 @@ class ExpressionEvaluatorTest : public ::testing::Test {
   ExpressionEvaluatorTest()
       : config(disk_test_utils::GenerateOnDiskConfig(testSuite)),
         db(new StorageType(config)),
-        storage_dba(db->Access()),
+        storage_dba(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)),
         dba(storage_dba.get()) {}
 
   ~ExpressionEvaluatorTest() override {
@@ -2075,9 +2075,10 @@ TYPED_TEST(FunctionTest, ToStringInteger) {
 }
 
 TYPED_TEST(FunctionTest, ToStringDouble) {
-  EXPECT_EQ(this->EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000");
-  EXPECT_EQ(this->EvaluateFunction("TOSTRING", 0.0).ValueString(), "0.000000");
-  EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231322");
+  EXPECT_EQ(this->EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000000000002");
+  EXPECT_EQ(this->EvaluateFunction("TOSTRING", 0.0).ValueString(), "0");
+  EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231321700004628");
+  EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.23132171234).ValueString(), "238910.231321712344652");
 }
 
 TYPED_TEST(FunctionTest, ToStringBool) {
diff --git a/tests/unit/query_hint_provider.cpp b/tests/unit/query_hint_provider.cpp
index 9973bf2ca..4165ef9d2 100644
--- a/tests/unit/query_hint_provider.cpp
+++ b/tests/unit/query_hint_provider.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -39,7 +39,7 @@ class HintProviderSuite : public ::testing::Test {
   int symbol_count = 0;
 
   void SetUp() {
-    storage_dba.emplace(db->Access());
+    storage_dba.emplace(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN));
     dba.emplace(storage_dba->get());
   }
 
diff --git a/tests/unit/query_plan_accumulate_aggregate.cpp b/tests/unit/query_plan_accumulate_aggregate.cpp
index b1e9a62d0..c8f1c30c9 100644
--- a/tests/unit/query_plan_accumulate_aggregate.cpp
+++ b/tests/unit/query_plan_accumulate_aggregate.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -25,6 +25,8 @@
 #include "storage/v2/disk/storage.hpp"
 #include "storage/v2/inmemory/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 using namespace memgraph::query;
 using namespace memgraph::query::plan;
 using memgraph::query::test_common::ToIntList;
@@ -95,7 +97,7 @@ TYPED_TEST(QueryPlanTest, Accumulate) {
     this->db.reset(nullptr);
     this->CleanStorageDirs();
     this->db = std::make_unique<TypeParam>(this->config);
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto prop = dba.NameToProperty("x");
 
@@ -148,7 +150,7 @@ TYPED_TEST(QueryPlanTest, AccumulateAdvance) {
     this->db.reset();
     this->CleanStorageDirs();
     this->db = std::make_unique<TypeParam>(this->config);
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     SymbolTable symbol_table;
     NodeCreationInfo node;
@@ -167,7 +169,7 @@ TYPED_TEST(QueryPlanTest, AccumulateAdvance) {
 template <typename StorageType>
 class QueryPlanAggregateOps : public QueryPlanTest<StorageType> {
  protected:
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   memgraph::storage::PropertyId prop = this->db->NameToProperty("prop");
 
@@ -308,7 +310,7 @@ TYPED_TEST(QueryPlanTest, AggregateGroupByValues) {
   // Tests that distinct groups are aggregated properly for values of all types.
   // Also test the "remember" part of the Aggregation API as final results are
   // obtained via a property lookup of a remembered node.
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // a vector of memgraph::storage::PropertyValue to be set as property values on vertices
@@ -370,7 +372,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupBy) {
   // in this test we have 3 different properties that have different values
   // for different records and assert that we get the correct combination
   // of values in our groups
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto prop1 = dba.NameToProperty("prop1");
@@ -401,7 +403,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupBy) {
 }
 
 TYPED_TEST(QueryPlanTest, AggregateNoInput) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -424,7 +426,7 @@ TYPED_TEST(QueryPlanTest, AggregateCountEdgeCases) {
   //  - 2 vertices in database, property set on one
   //  - 2 vertices in database, property set on both
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto prop = dba.NameToProperty("prop");
 
@@ -476,7 +478,7 @@ TYPED_TEST(QueryPlanTest, AggregateFirstValueTypes) {
   // testing exceptions that get emitted by the first-value
   // type check
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto v1 = dba.InsertVertex();
@@ -528,7 +530,7 @@ TYPED_TEST(QueryPlanTest, AggregateTypes) {
   // does not check all combinations that can result in an exception
   // (that logic is defined and tested by TypedValue)
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto p1 = dba.NameToProperty("p1");  // has only string props
@@ -581,7 +583,7 @@ TYPED_TEST(QueryPlanTest, AggregateTypes) {
 }
 
 TYPED_TEST(QueryPlanTest, Unwind) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -717,7 +719,7 @@ TYPED_TEST(QueryPlanTest, AggregateGroupByValuesWithDistinct) {
   // Tests that distinct groups are aggregated properly for values of all types.
   // Also test the "remember" part of the Aggregation API as final results are
   // obtained via a property lookup of a remembered node.
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // a vector of memgraph::storage::PropertyValue to be set as property values on vertices
@@ -782,7 +784,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupByWithDistinct) {
   // in this test we have 3 different properties that have different values
   // for different records and assert that we get the correct combination
   // of values in our groups
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto prop1 = dba.NameToProperty("prop1");
@@ -814,7 +816,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupByWithDistinct) {
 }
 
 TYPED_TEST(QueryPlanTest, AggregateNoInputWithDistinct) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -837,7 +839,7 @@ TYPED_TEST(QueryPlanTest, AggregateCountEdgeCasesWithDistinct) {
   //  - 2 vertices in database, property set on one
   //  - 2 vertices in database, property set on both
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto prop = dba.NameToProperty("prop");
 
@@ -889,7 +891,7 @@ TYPED_TEST(QueryPlanTest, AggregateFirstValueTypesWithDistinct) {
   // testing exceptions that get emitted by the first-value
   // type check
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto v1 = dba.InsertVertex();
@@ -941,7 +943,7 @@ TYPED_TEST(QueryPlanTest, AggregateTypesWithDistinct) {
   // does not check all combinations that can result in an exception
   // (that logic is defined and tested by TypedValue)
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto p1 = dba.NameToProperty("p1");  // has only string props
diff --git a/tests/unit/query_plan_bag_semantics.cpp b/tests/unit/query_plan_bag_semantics.cpp
index 6b1c7ab64..4f3bd5256 100644
--- a/tests/unit/query_plan_bag_semantics.cpp
+++ b/tests/unit/query_plan_bag_semantics.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -31,6 +31,7 @@
 
 #include "query_plan_common.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using namespace memgraph::query;
 using namespace memgraph::query::plan;
 
@@ -53,7 +54,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra
 TYPED_TEST_CASE(QueryPlanTest, StorageTypes);
 
 TYPED_TEST(QueryPlanTest, Skip) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -81,7 +82,7 @@ TYPED_TEST(QueryPlanTest, Skip) {
 }
 
 TYPED_TEST(QueryPlanTest, Limit) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -112,7 +113,7 @@ TYPED_TEST(QueryPlanTest, CreateLimit) {
   // CREATE (n), (m)
   // MATCH (n) CREATE (m) LIMIT 1
   // in the end we need to have 3 vertices in the db
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   dba.InsertVertex();
   dba.InsertVertex();
@@ -133,7 +134,7 @@ TYPED_TEST(QueryPlanTest, CreateLimit) {
 }
 
 TYPED_TEST(QueryPlanTest, OrderBy) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   auto prop = dba.NameToProperty("prop");
@@ -204,7 +205,7 @@ TYPED_TEST(QueryPlanTest, OrderBy) {
 }
 
 TYPED_TEST(QueryPlanTest, OrderByMultiple) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -256,7 +257,7 @@ TYPED_TEST(QueryPlanTest, OrderByMultiple) {
 }
 
 TYPED_TEST(QueryPlanTest, OrderByExceptions) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   auto prop = dba.NameToProperty("prop");
diff --git a/tests/unit/query_plan_create_set_remove_delete.cpp b/tests/unit/query_plan_create_set_remove_delete.cpp
index d0127c7c6..1fa400940 100644
--- a/tests/unit/query_plan_create_set_remove_delete.cpp
+++ b/tests/unit/query_plan_create_set_remove_delete.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -38,6 +38,7 @@
 
 using namespace memgraph::query;
 using namespace memgraph::query::plan;
+using memgraph::replication_coordination_glue::ReplicationRole;
 
 template <typename StorageType>
 class QueryPlanTest : public testing::Test {
@@ -58,7 +59,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra
 TYPED_TEST_CASE(QueryPlanTest, StorageTypes);
 
 TYPED_TEST(QueryPlanTest, CreateNodeWithAttributes) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   memgraph::storage::LabelId label = dba.NameToLabel("Person");
@@ -102,7 +103,7 @@ TYPED_TEST(QueryPlanTest, CreateNodeWithAttributes) {
 TYPED_TEST(QueryPlanTest, FineGrainedCreateNodeWithAttributes) {
   memgraph::license::global_license_checker.EnableTesting();
   memgraph::query::SymbolTable symbol_table;
-  auto dba = this->db->Access();
+  auto dba = this->db->Access(ReplicationRole::MAIN);
   DbAccessor execution_dba(dba.get());
   const auto label = dba->NameToLabel("label1");
   const auto property = memgraph::storage::PropertyId::FromInt(1);
@@ -142,7 +143,7 @@ TYPED_TEST(QueryPlanTest, FineGrainedCreateNodeWithAttributes) {
 
 TYPED_TEST(QueryPlanTest, CreateReturn) {
   // test CREATE (n:Person {age: 42}) RETURN n, n.age
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   memgraph::storage::LabelId label = dba.NameToLabel("Person");
@@ -182,7 +183,7 @@ TYPED_TEST(QueryPlanTest, FineGrainedCreateReturn) {
   memgraph::license::global_license_checker.EnableTesting();
 
   // test CREATE (n:Person {age: 42}) RETURN n, n.age
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   const auto label = dba.NameToLabel("label");
@@ -237,7 +238,7 @@ TYPED_TEST(QueryPlanTest, FineGrainedCreateReturn) {
 #endif
 
 TYPED_TEST(QueryPlanTest, CreateExpand) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   memgraph::storage::LabelId label_node_1 = dba.NameToLabel("Node1");
@@ -315,7 +316,7 @@ TYPED_TEST(QueryPlanTest, CreateExpand) {
 template <typename StorageType>
 class CreateExpandWithAuthFixture : public QueryPlanTest<StorageType> {
  protected:
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
 
@@ -445,7 +446,7 @@ TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithCycleWithEverythingGrant
 }
 
 TYPED_TEST(QueryPlanTest, MatchCreateNode) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // add three nodes we'll match and expand-create from
@@ -473,7 +474,7 @@ TYPED_TEST(QueryPlanTest, MatchCreateNode) {
 template <typename StorageType>
 class MatchCreateNodeWithAuthFixture : public QueryPlanTest<StorageType> {
  protected:
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
 
@@ -553,7 +554,7 @@ TYPED_TEST(MatchCreateNodeWithAuthFixture, MatchCreateWithOneLabelDeniedThrows)
 #endif
 
 TYPED_TEST(QueryPlanTest, MatchCreateExpand) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // add three nodes we'll match and expand-create from
@@ -601,7 +602,7 @@ TYPED_TEST(QueryPlanTest, MatchCreateExpand) {
 template <typename StorageType>
 class MatchCreateExpandWithAuthFixture : public QueryPlanTest<StorageType> {
  protected:
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
 
@@ -746,7 +747,7 @@ TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesW
 #endif
 
 TYPED_TEST(QueryPlanTest, Delete) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // make a fully-connected (one-direction, no cycles) with 4 nodes
@@ -818,7 +819,7 @@ TYPED_TEST(QueryPlanTest, Delete) {
 template <typename StorageType>
 class DeleteOperatorWithAuthFixture : public QueryPlanTest<StorageType> {
  protected:
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
 
@@ -978,7 +979,7 @@ TYPED_TEST(QueryPlanTest, DeleteTwiceDeleteBlockingEdge) {
   // MATCH (n)-[r]-(m) [DETACH] DELETE n, r, m
 
   auto test_delete = [this](bool detach) {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
 
     auto v1 = dba.InsertVertex();
@@ -1013,7 +1014,7 @@ TYPED_TEST(QueryPlanTest, DeleteTwiceDeleteBlockingEdge) {
 
 TYPED_TEST(QueryPlanTest, DeleteReturn) {
   // MATCH (n) DETACH DELETE n RETURN n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // graph with 4 vertices
@@ -1047,7 +1048,7 @@ TYPED_TEST(QueryPlanTest, DeleteReturn) {
 
 TYPED_TEST(QueryPlanTest, DeleteNull) {
   // test (simplified) WITH Null as x delete x
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -1071,7 +1072,7 @@ TYPED_TEST(QueryPlanTest, DeleteAdvance) {
   auto advance = std::make_shared<Accumulate>(delete_op, std::vector<Symbol>{n.sym_}, true);
   auto res_sym = symbol_table.CreateSymbol("res", true);
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     dba.InsertVertex();
     dba.AdvanceCommand();
@@ -1080,7 +1081,7 @@ TYPED_TEST(QueryPlanTest, DeleteAdvance) {
     EXPECT_EQ(1, PullAll(*produce, &context));
   }
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     dba.InsertVertex();
     dba.AdvanceCommand();
@@ -1092,7 +1093,7 @@ TYPED_TEST(QueryPlanTest, DeleteAdvance) {
 }
 
 TYPED_TEST(QueryPlanTest, SetProperty) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // graph with 4 vertices in connected pairs
@@ -1148,7 +1149,7 @@ TYPED_TEST(QueryPlanTest, SetProperty) {
 
 TYPED_TEST(QueryPlanTest, SetProperties) {
   auto test_set_properties = [this](bool update) {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
 
     // graph: ({a: 0})-[:R {b:1}]->({c:2})
@@ -1221,7 +1222,7 @@ TYPED_TEST(QueryPlanTest, SetProperties) {
 }
 
 TYPED_TEST(QueryPlanTest, SetLabels) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto label1 = dba.NameToLabel("label1");
@@ -1271,7 +1272,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) {
     memgraph::auth::User user{"test"};
     user.fine_grained_access_handler().label_permissions().Grant("*",
                                                                  memgraph::auth::FineGrainedPermission::CREATE_DELETE);
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto label1 = dba.NameToLabel("label1");
     auto label2 = dba.NameToLabel("label2");
@@ -1288,7 +1289,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) {
   {
     memgraph::auth::User user{"test"};
     user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE);
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto label1 = dba.NameToLabel("label1");
     auto label2 = dba.NameToLabel("label2");
@@ -1307,7 +1308,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) {
     user.fine_grained_access_handler().label_permissions().Grant("label3",
                                                                  memgraph::auth::FineGrainedPermission::CREATE_DELETE);
 
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto label1 = dba.NameToLabel("label1");
     auto label2 = dba.NameToLabel("label2");
@@ -1319,7 +1320,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) {
 #endif
 
 TYPED_TEST(QueryPlanTest, RemoveProperty) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // graph with 4 vertices in connected pairs
@@ -1381,7 +1382,7 @@ TYPED_TEST(QueryPlanTest, RemoveProperty) {
 }
 
 TYPED_TEST(QueryPlanTest, RemoveLabels) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto label1 = dba.NameToLabel("label1");
@@ -1442,7 +1443,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) {
     memgraph::auth::User user{"test"};
     user.fine_grained_access_handler().label_permissions().Grant("*",
                                                                  memgraph::auth::FineGrainedPermission::CREATE_DELETE);
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto label1 = dba.NameToLabel("label1");
     auto label2 = dba.NameToLabel("label2");
@@ -1459,7 +1460,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) {
   {
     memgraph::auth::User user{"test"};
     user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE);
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto label1 = dba.NameToLabel("label1");
     auto label2 = dba.NameToLabel("label2");
@@ -1478,7 +1479,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) {
     user.fine_grained_access_handler().label_permissions().Grant("label3",
                                                                  memgraph::auth::FineGrainedPermission::CREATE_DELETE);
 
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto label1 = dba.NameToLabel("label1");
     auto label2 = dba.NameToLabel("label2");
@@ -1490,7 +1491,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) {
 #endif
 
 TYPED_TEST(QueryPlanTest, NodeFilterSet) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Create a graph such that (v1 {prop: 42}) is connected to v2 and v3.
   auto v1 = dba.InsertVertex();
@@ -1528,7 +1529,7 @@ TYPED_TEST(QueryPlanTest, NodeFilterSet) {
 }
 
 TYPED_TEST(QueryPlanTest, FilterRemove) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Create a graph such that (v1 {prop: 42}) is connected to v2 and v3.
   auto v1 = dba.InsertVertex();
@@ -1562,7 +1563,7 @@ TYPED_TEST(QueryPlanTest, FilterRemove) {
 }
 
 TYPED_TEST(QueryPlanTest, SetRemove) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto v = dba.InsertVertex();
   auto label1 = dba.NameToLabel("label1");
@@ -1591,7 +1592,7 @@ TYPED_TEST(QueryPlanTest, Merge) {
   //  - merge_match branch looks for an expansion (any direction)
   //    and sets some property (for result validation)
   //  - merge_create branch just sets some other property
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto v1 = dba.InsertVertex();
   auto v2 = dba.InsertVertex();
@@ -1633,7 +1634,7 @@ TYPED_TEST(QueryPlanTest, Merge) {
 TYPED_TEST(QueryPlanTest, MergeNoInput) {
   // merge with no input, creates a single node
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -1651,7 +1652,7 @@ TYPED_TEST(QueryPlanTest, MergeNoInput) {
 
 TYPED_TEST(QueryPlanTest, SetPropertyWithCaching) {
   // SET (Null).prop = 42
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   auto prop = PROPERTY_PAIR(dba, "property");
@@ -1666,7 +1667,7 @@ TYPED_TEST(QueryPlanTest, SetPropertyWithCaching) {
 
 TYPED_TEST(QueryPlanTest, SetPropertyOnNull) {
   // SET (Null).prop = 42
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   auto prop = PROPERTY_PAIR(dba, "property");
@@ -1681,7 +1682,7 @@ TYPED_TEST(QueryPlanTest, SetPropertyOnNull) {
 
 TYPED_TEST(QueryPlanTest, SetPropertiesOnNull) {
   // OPTIONAL MATCH (n) SET n = n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   auto n = MakeScanAll(this->storage, symbol_table, "n");
@@ -1694,7 +1695,7 @@ TYPED_TEST(QueryPlanTest, SetPropertiesOnNull) {
 }
 
 TYPED_TEST(QueryPlanTest, UpdateSetPropertiesFromMap) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a single vertex. ( {property: 43})
   auto vertex_accessor = dba.InsertVertex();
@@ -1729,7 +1730,7 @@ TYPED_TEST(QueryPlanTest, UpdateSetPropertiesFromMap) {
 }
 
 TYPED_TEST(QueryPlanTest, SetPropertiesFromMapWithCaching) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // Add a single vertex. ({prop1: 43, prop2: 44})
@@ -1769,7 +1770,7 @@ TYPED_TEST(QueryPlanTest, SetPropertiesFromMapWithCaching) {
 
 TYPED_TEST(QueryPlanTest, SetLabelsOnNull) {
   // OPTIONAL MATCH (n) SET n :label
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto label = dba.NameToLabel("label");
   SymbolTable symbol_table;
@@ -1783,7 +1784,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsOnNull) {
 
 TYPED_TEST(QueryPlanTest, RemovePropertyOnNull) {
   // REMOVE (Null).prop
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   auto prop = PROPERTY_PAIR(dba, "property");
@@ -1797,7 +1798,7 @@ TYPED_TEST(QueryPlanTest, RemovePropertyOnNull) {
 
 TYPED_TEST(QueryPlanTest, RemoveLabelsOnNull) {
   // OPTIONAL MATCH (n) REMOVE n :label
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto label = dba.NameToLabel("label");
   SymbolTable symbol_table;
@@ -1812,7 +1813,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsOnNull) {
 
 TYPED_TEST(QueryPlanTest, DeleteSetProperty) {
   // MATCH (n) DELETE n SET n.property = 42 RETURN n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a single vertex.
   dba.InsertVertex();
@@ -1838,7 +1839,7 @@ TYPED_TEST(QueryPlanTest, DeleteSetProperty) {
 TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFromMap) {
   // MATCH (n) DELETE n SET n = {property: 42} return n
   // MATCH (n) DELETE n SET n += {property: 42} return n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a single vertex.
   dba.InsertVertex();
@@ -1866,7 +1867,7 @@ TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFromMap) {
 
 TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFrom) {
   // MATCH (n) DELETE n SET n = n RETURN n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a single vertex.
   {
@@ -1895,7 +1896,7 @@ TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFrom) {
 
 TYPED_TEST(QueryPlanTest, DeleteRemoveLabels) {
   // MATCH (n) DELETE n REMOVE n :label return n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a single vertex.
   dba.InsertVertex();
@@ -1920,7 +1921,7 @@ TYPED_TEST(QueryPlanTest, DeleteRemoveLabels) {
 
 TYPED_TEST(QueryPlanTest, DeleteRemoveProperty) {
   // MATCH (n) DELETE n REMOVE n.property RETURN n
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a single vertex.
   dba.InsertVertex();
@@ -1950,7 +1951,7 @@ TYPED_TEST(QueryPlanTest, DeleteRemoveProperty) {
 template <typename StorageType>
 class UpdatePropertiesWithAuthFixture : public QueryPlanTest<StorageType> {
  protected:
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
 
@@ -2587,7 +2588,7 @@ class DynamicExpandFixture : public testing::Test {
   const std::string testSuite = "query_plan_create_set_remove_delete_dynamic_expand";
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
   AstStorage storage;
diff --git a/tests/unit/query_plan_match_filter_return.cpp b/tests/unit/query_plan_match_filter_return.cpp
index 6b856704d..d5468b6b5 100644
--- a/tests/unit/query_plan_match_filter_return.cpp
+++ b/tests/unit/query_plan_match_filter_return.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -42,6 +42,7 @@
 
 using namespace memgraph::query;
 using namespace memgraph::query::plan;
+using memgraph::replication_coordination_glue::ReplicationRole;
 
 const std::string testSuite = "query_plan_match_filter_return";
 
@@ -50,7 +51,7 @@ class MatchReturnFixture : public testing::Test {
  protected:
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   AstStorage storage;
   SymbolTable symbol_table;
@@ -239,7 +240,7 @@ class QueryPlan : public testing::Test {
 TYPED_TEST_CASE(QueryPlan, StorageTypes);
 
 TYPED_TEST(QueryPlan, MatchReturnCartesian) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   ASSERT_TRUE(dba.InsertVertex().AddLabel(dba.NameToLabel("l1")).HasValue());
@@ -263,7 +264,7 @@ TYPED_TEST(QueryPlan, MatchReturnCartesian) {
 }
 
 TYPED_TEST(QueryPlan, StandaloneReturn) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // add a few nodes to the database
@@ -285,7 +286,7 @@ TYPED_TEST(QueryPlan, StandaloneReturn) {
 }
 
 TYPED_TEST(QueryPlan, NodeFilterLabelsAndProperties) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // add a few nodes to the database
@@ -337,7 +338,7 @@ TYPED_TEST(QueryPlan, NodeFilterLabelsAndProperties) {
 }
 
 TYPED_TEST(QueryPlan, NodeFilterMultipleLabels) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // add a few nodes to the database
@@ -382,7 +383,7 @@ TYPED_TEST(QueryPlan, NodeFilterMultipleLabels) {
 }
 
 TYPED_TEST(QueryPlan, Cartesian) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto add_vertex = [&dba](std::string label) {
@@ -419,7 +420,7 @@ TYPED_TEST(QueryPlan, Cartesian) {
 }
 
 TYPED_TEST(QueryPlan, CartesianEmptySet) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -439,7 +440,7 @@ TYPED_TEST(QueryPlan, CartesianEmptySet) {
 }
 
 TYPED_TEST(QueryPlan, CartesianThreeWay) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto add_vertex = [&dba](std::string label) {
     auto vertex = dba.InsertVertex();
@@ -489,7 +490,7 @@ class ExpandFixture : public testing::Test {
  protected:
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   SymbolTable symbol_table;
   AstStorage storage;
@@ -649,7 +650,7 @@ class QueryPlanExpandVariable : public testing::Test {
 
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   // labels for layers in the double chain
   std::vector<memgraph::storage::LabelId> labels;
@@ -1804,7 +1805,7 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test {
  protected:
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property");
   memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type");
@@ -2248,7 +2249,7 @@ class QueryPlanExpandAllShortestPaths : public testing::Test {
  protected:
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property");
   memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type");
@@ -2699,7 +2700,7 @@ TYPED_TEST(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) {
 #endif
 
 TYPED_TEST(QueryPlan, ExpandOptional) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   SymbolTable symbol_table;
@@ -2750,7 +2751,7 @@ TYPED_TEST(QueryPlan, ExpandOptional) {
 }
 
 TYPED_TEST(QueryPlan, OptionalMatchEmptyDB) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -2767,7 +2768,7 @@ TYPED_TEST(QueryPlan, OptionalMatchEmptyDB) {
 }
 
 TYPED_TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
   // OPTIONAL MATCH (n)
@@ -2790,7 +2791,7 @@ TYPED_TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) {
 }
 
 TYPED_TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Make a graph with 2 connected, unlabeled nodes.
   auto v1 = dba.InsertVertex();
@@ -2834,7 +2835,7 @@ TYPED_TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) {
 }
 
 TYPED_TEST(QueryPlan, ExpandExistingNode) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // make a graph (v1)->(v2) that
@@ -2872,7 +2873,7 @@ TYPED_TEST(QueryPlan, ExpandExistingNode) {
 TYPED_TEST(QueryPlan, ExpandBothCycleEdgeCase) {
   // we're testing that expanding on BOTH
   // does only one expansion for a cycle
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto v = dba.InsertVertex();
@@ -2889,7 +2890,7 @@ TYPED_TEST(QueryPlan, ExpandBothCycleEdgeCase) {
 }
 
 TYPED_TEST(QueryPlan, EdgeFilter) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // make an N-star expanding from (v1)
@@ -2949,7 +2950,7 @@ TYPED_TEST(QueryPlan, EdgeFilter) {
 }
 
 TYPED_TEST(QueryPlan, EdgeFilterMultipleTypes) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto v1 = dba.InsertVertex();
@@ -2979,7 +2980,7 @@ TYPED_TEST(QueryPlan, EdgeFilterMultipleTypes) {
 }
 
 TYPED_TEST(QueryPlan, Filter) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // add a 6 nodes with property 'prop', 2 have true as value
@@ -3003,7 +3004,7 @@ TYPED_TEST(QueryPlan, Filter) {
 }
 
 TYPED_TEST(QueryPlan, EdgeUniquenessFilter) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   // make a graph that has (v1)->(v2) and a recursive edge (v1)->(v1)
@@ -3038,7 +3039,7 @@ TYPED_TEST(QueryPlan, Distinct) {
   // test queries like
   // UNWIND [1, 2, 3, 3] AS x RETURN DISTINCT x
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   SymbolTable symbol_table;
 
@@ -3082,11 +3083,11 @@ TYPED_TEST(QueryPlan, Distinct) {
 TYPED_TEST(QueryPlan, ScanAllByLabel) {
   auto label = this->db->NameToLabel("label");
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Add a vertex with a label and one without.
   auto labeled_vertex = dba.InsertVertex();
@@ -3133,7 +3134,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelProperty) {
       memgraph::storage::PropertyValue(
           std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(2)})};
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     for (const auto &value : values) {
       auto vertex = dba.InsertVertex();
@@ -3144,12 +3145,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelProperty) {
   }
 
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   ASSERT_EQ(14, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
 
@@ -3243,7 +3244,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) {
   auto label = this->db->NameToLabel("label");
   auto prop = this->db->NameToProperty("prop");
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto number_vertex = dba.InsertVertex();
     ASSERT_TRUE(number_vertex.AddLabel(label).HasValue());
@@ -3254,12 +3255,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) {
     ASSERT_FALSE(dba.Commit().HasError());
   }
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
   // MATCH (n :label {prop: 42})
@@ -3283,7 +3284,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyValueError) {
   auto label = this->db->NameToLabel("label");
   auto prop = this->db->NameToProperty("prop");
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     for (int i = 0; i < 2; ++i) {
       auto vertex = dba.InsertVertex();
@@ -3293,12 +3294,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyValueError) {
     ASSERT_FALSE(dba.Commit().HasError());
   }
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
   // MATCH (m), (n :label {prop: m})
@@ -3316,7 +3317,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeError) {
   auto label = this->db->NameToLabel("label");
   auto prop = this->db->NameToProperty("prop");
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     for (int i = 0; i < 2; ++i) {
       auto vertex = dba.InsertVertex();
@@ -3326,12 +3327,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeError) {
     ASSERT_FALSE(dba.Commit().HasError());
   }
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
   // MATCH (m), (n :label {prop: m})
@@ -3372,7 +3373,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) {
   auto label = this->db->NameToLabel("label");
   auto prop = this->db->NameToProperty("prop");
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto vertex = dba.InsertVertex();
     ASSERT_TRUE(vertex.AddLabel(label).HasValue());
@@ -3382,12 +3383,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) {
     ASSERT_FALSE(dba.Commit().HasError());
   }
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
   // MATCH (n :label {prop: 42})
@@ -3409,7 +3410,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) {
   auto label = this->db->NameToLabel("label");
   auto prop = this->db->NameToProperty("prop");
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto vertex = dba.InsertVertex();
     ASSERT_TRUE(vertex.AddLabel(label).HasValue());
@@ -3419,12 +3420,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) {
     ASSERT_FALSE(dba.Commit().HasError());
   }
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
   // MATCH (n :label) WHERE null <= n.prop < null
@@ -3444,7 +3445,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) {
   auto label = this->db->NameToLabel("label");
   auto prop = this->db->NameToProperty("prop");
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto v = dba.InsertVertex();
     ASSERT_TRUE(v.AddLabel(label).HasValue());
@@ -3452,12 +3453,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) {
     ASSERT_FALSE(dba.Commit().HasError());
   }
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
 
@@ -3487,7 +3488,7 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
   const int prop_value1 = 42, prop_value2 = 69;
 
   for (int i = 0; i < vertex_count; ++i) {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto v = dba.InsertVertex();
     ASSERT_TRUE(v.AddLabel(label).HasValue());
@@ -3497,14 +3498,14 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
   }
 
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
   // Make sure there are `vertex_count` vertices
   {
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     EXPECT_EQ(vertex_count, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
   }
@@ -3512,7 +3513,7 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
   // Make sure there are `vertex_prop_count` results when using index
   auto count_with_index = [this, &label, &prop](int prop_value, int prop_count) {
     SymbolTable symbol_table;
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto scan_all_by_label_property_value =
         MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", LITERAL(prop_value));
@@ -3526,7 +3527,7 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
   // Make sure there are `vertex_count` results when using scan all
   auto count_with_scan_all = [this, &prop](int prop_value, int prop_count) {
     SymbolTable symbol_table;
-    auto storage_dba = this->db->Access();
+    auto storage_dba = this->db->Access(ReplicationRole::MAIN);
     memgraph::query::DbAccessor dba(storage_dba.get());
     auto scan_all = MakeScanAll(this->storage, symbol_table, "n");
     auto e = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), std::make_pair("prop", prop));
@@ -3551,7 +3552,7 @@ class ExistsFixture : public testing::Test {
  protected:
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   AstStorage storage;
   SymbolTable symbol_table;
@@ -3779,7 +3780,7 @@ class SubqueriesFeature : public testing::Test {
  protected:
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   AstStorage storage;
   SymbolTable symbol_table;
diff --git a/tests/unit/query_plan_operator_to_string.cpp b/tests/unit/query_plan_operator_to_string.cpp
index 27d27f0d1..694552cf0 100644
--- a/tests/unit/query_plan_operator_to_string.cpp
+++ b/tests/unit/query_plan_operator_to_string.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -37,7 +37,7 @@ class OperatorToStringTest : public ::testing::Test {
   OperatorToStringTest()
       : config(disk_test_utils::GenerateOnDiskConfig(testSuite)),
         db(new StorageType(config)),
-        dba_storage(db->Access()),
+        dba_storage(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)),
         dba(dba_storage.get()) {}
 
   ~OperatorToStringTest() override {
diff --git a/tests/unit/query_plan_read_write_typecheck.cpp b/tests/unit/query_plan_read_write_typecheck.cpp
index a369f44b9..f9f14902b 100644
--- a/tests/unit/query_plan_read_write_typecheck.cpp
+++ b/tests/unit/query_plan_read_write_typecheck.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -36,7 +36,8 @@ class ReadWriteTypeCheckTest : public ::testing::Test {
 
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage{
+      db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{dba_storage.get()};
 
   void TearDown() override {
diff --git a/tests/unit/query_plan_v2_create_set_remove_delete.cpp b/tests/unit/query_plan_v2_create_set_remove_delete.cpp
index 7aeeb4e5e..b82454682 100644
--- a/tests/unit/query_plan_v2_create_set_remove_delete.cpp
+++ b/tests/unit/query_plan_v2_create_set_remove_delete.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -18,6 +18,7 @@
 #include "query/plan/operator.hpp"
 #include "storage/v2/disk/storage.hpp"
 #include "storage/v2/inmemory/storage.hpp"
+using memgraph::replication_coordination_glue::ReplicationRole;
 
 template <typename StorageType>
 class QueryPlan : public testing::Test {
@@ -37,7 +38,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra
 TYPED_TEST_CASE(QueryPlan, StorageTypes);
 
 TYPED_TEST(QueryPlan, CreateNodeWithAttributes) {
-  auto dba = this->db->Access();
+  auto dba = this->db->Access(ReplicationRole::MAIN);
 
   auto label = memgraph::storage::LabelId::FromInt(42);
   auto property = memgraph::storage::PropertyId::FromInt(1);
@@ -75,7 +76,7 @@ TYPED_TEST(QueryPlan, CreateNodeWithAttributes) {
 TYPED_TEST(QueryPlan, ScanAllEmpty) {
   memgraph::query::AstStorage ast;
   memgraph::query::SymbolTable symbol_table;
-  auto dba = this->db->Access();
+  auto dba = this->db->Access(ReplicationRole::MAIN);
   DbAccessor execution_dba(dba.get());
   auto node_symbol = symbol_table.CreateSymbol("n", true);
   {
@@ -100,13 +101,13 @@ TYPED_TEST(QueryPlan, ScanAllEmpty) {
 
 TYPED_TEST(QueryPlan, ScanAll) {
   {
-    auto dba = this->db->Access();
+    auto dba = this->db->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 42; ++i) dba->CreateVertex();
     EXPECT_FALSE(dba->Commit().HasError());
   }
   memgraph::query::AstStorage ast;
   memgraph::query::SymbolTable symbol_table;
-  auto dba = this->db->Access();
+  auto dba = this->db->Access(ReplicationRole::MAIN);
   DbAccessor execution_dba(dba.get());
   auto node_symbol = symbol_table.CreateSymbol("n", true);
   memgraph::query::plan::ScanAll scan_all(nullptr, node_symbol);
@@ -121,12 +122,12 @@ TYPED_TEST(QueryPlan, ScanAll) {
 TYPED_TEST(QueryPlan, ScanAllByLabel) {
   auto label = this->db->NameToLabel("label");
   {
-    auto unique_acc = this->db->UniqueAccess();
+    auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(label).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto dba = this->db->Access();
+    auto dba = this->db->Access(ReplicationRole::MAIN);
     // Add some unlabeled vertices
     for (int i = 0; i < 12; ++i) dba->CreateVertex();
     // Add labeled vertices
@@ -136,7 +137,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabel) {
     }
     EXPECT_FALSE(dba->Commit().HasError());
   }
-  auto dba = this->db->Access();
+  auto dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::AstStorage ast;
   memgraph::query::SymbolTable symbol_table;
   auto node_symbol = symbol_table.CreateSymbol("n", true);
diff --git a/tests/unit/query_pretty_print.cpp b/tests/unit/query_pretty_print.cpp
index 1b19f8aa8..ac789b1da 100644
--- a/tests/unit/query_pretty_print.cpp
+++ b/tests/unit/query_pretty_print.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -36,7 +36,8 @@ class ExpressionPrettyPrinterTest : public ::testing::Test {
   const std::string testSuite = "query_pretty_print";
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{
+      db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   AstStorage storage;
 
diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp
index c5f30f3af..e12a61f28 100644
--- a/tests/unit/query_procedure_mgp_type.cpp
+++ b/tests/unit/query_procedure_mgp_type.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,6 +23,8 @@
 #include "disk_test_utils.hpp"
 #include "test_utils.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 template <typename StorageType>
 class CypherType : public testing::Test {
  public:
@@ -244,7 +246,7 @@ TYPED_TEST(CypherType, MapSatisfiesType) {
 }
 
 TYPED_TEST(CypherType, VertexSatisfiesType) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto vertex = dba.InsertVertex();
   mgp_memory memory{memgraph::utils::NewDeleteResource()};
@@ -267,7 +269,7 @@ TYPED_TEST(CypherType, VertexSatisfiesType) {
 }
 
 TYPED_TEST(CypherType, EdgeSatisfiesType) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto v1 = dba.InsertVertex();
   auto v2 = dba.InsertVertex();
@@ -291,7 +293,7 @@ TYPED_TEST(CypherType, EdgeSatisfiesType) {
 }
 
 TYPED_TEST(CypherType, PathSatisfiesType) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto v1 = dba.InsertVertex();
   auto v2 = dba.InsertVertex();
diff --git a/tests/unit/query_procedure_py_module.cpp b/tests/unit/query_procedure_py_module.cpp
index dd744229a..baef2e1c8 100644
--- a/tests/unit/query_procedure_py_module.cpp
+++ b/tests/unit/query_procedure_py_module.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -21,6 +21,8 @@
 #include "storage/v2/inmemory/storage.hpp"
 #include "test_utils.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 template <typename StorageType>
 class PyModule : public testing::Test {
  public:
@@ -116,7 +118,7 @@ static void AssertPickleAndCopyAreNotSupported(PyObject *py_obj) {
 TYPED_TEST(PyModule, PyVertex) {
   // Initialize the database with 2 vertices and 1 edge.
   {
-    auto dba = this->db->Access();
+    auto dba = this->db->Access(ReplicationRole::MAIN);
     auto v1 = dba->CreateVertex();
     auto v2 = dba->CreateVertex();
 
@@ -129,7 +131,7 @@ TYPED_TEST(PyModule, PyVertex) {
     ASSERT_FALSE(dba->Commit().HasError());
   }
   // Get the first vertex as an mgp_value.
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   mgp_memory memory{memgraph::utils::NewDeleteResource()};
   mgp_graph graph{&dba, memgraph::storage::View::OLD, nullptr, dba.GetStorageMode()};
@@ -165,7 +167,7 @@ TYPED_TEST(PyModule, PyVertex) {
 TYPED_TEST(PyModule, PyEdge) {
   // Initialize the database with 2 vertices and 1 edge.
   {
-    auto dba = this->db->Access();
+    auto dba = this->db->Access(ReplicationRole::MAIN);
     auto v1 = dba->CreateVertex();
     auto v2 = dba->CreateVertex();
 
@@ -179,7 +181,7 @@ TYPED_TEST(PyModule, PyEdge) {
     ASSERT_FALSE(dba->Commit().HasError());
   }
   // Get the edge as an mgp_value.
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   mgp_memory memory{memgraph::utils::NewDeleteResource()};
   mgp_graph graph{&dba, memgraph::storage::View::OLD, nullptr, dba.GetStorageMode()};
@@ -219,13 +221,13 @@ TYPED_TEST(PyModule, PyEdge) {
 
 TYPED_TEST(PyModule, PyPath) {
   {
-    auto dba = this->db->Access();
+    auto dba = this->db->Access(ReplicationRole::MAIN);
     auto v1 = dba->CreateVertex();
     auto v2 = dba->CreateVertex();
     ASSERT_TRUE(dba->CreateEdge(&v1, &v2, dba->NameToEdgeType("type")).HasValue());
     ASSERT_FALSE(dba->Commit().HasError());
   }
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   mgp_memory memory{memgraph::utils::NewDeleteResource()};
   mgp_graph graph{&dba, memgraph::storage::View::OLD, nullptr, dba.GetStorageMode()};
diff --git a/tests/unit/query_procedures_mgp_graph.cpp b/tests/unit/query_procedures_mgp_graph.cpp
index 785aab2cf..cf3b5a137 100644
--- a/tests/unit/query_procedures_mgp_graph.cpp
+++ b/tests/unit/query_procedures_mgp_graph.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -34,6 +34,8 @@
 #include "utils/memory.hpp"
 #include "utils/variant_helpers.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 #define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, mgp_error::MGP_ERROR_NO_ERROR)
 
 namespace {
@@ -155,7 +157,7 @@ class MgpGraphTest : public ::testing::Test {
   }
 
   memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) {
-    accessors_.push_back(storage->Access(isolationLevel));
+    accessors_.push_back(storage->Access(ReplicationRole::MAIN, isolationLevel));
     db_accessors_.emplace_back(accessors_.back().get());
     return db_accessors_.back();
   }
@@ -194,7 +196,8 @@ TYPED_TEST(MgpGraphTest, CreateVertex) {
     return;
   }
   mgp_graph graph = this->CreateGraph();
-  auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+  auto read_uncommited_accessor =
+      this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
   EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 0);
   MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &this->memory)};
   EXPECT_NE(vertex, nullptr);
@@ -217,7 +220,8 @@ TYPED_TEST(MgpGraphTest, DeleteVertex) {
     ASSERT_FALSE(accessor.Commit().HasError());
   }
   mgp_graph graph = this->CreateGraph();
-  auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+  auto read_uncommited_accessor =
+      this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
   EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1);
   MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
                                           mgp_vertex_id{vertex_id.AsInt()}, &this->memory)};
@@ -233,7 +237,8 @@ TYPED_TEST(MgpGraphTest, DetachDeleteVertex) {
   }
   const auto vertex_ids = this->CreateEdge();
   auto graph = this->CreateGraph();
-  auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+  auto read_uncommited_accessor =
+      this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
   EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 2);
   MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
                                           mgp_vertex_id{vertex_ids.front().AsInt()}, &this->memory)};
@@ -255,7 +260,8 @@ TYPED_TEST(MgpGraphTest, CreateDeleteWithImmutableGraph) {
     vertex_id = vertex.Gid();
     ASSERT_FALSE(accessor.Commit().HasError());
   }
-  auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+  auto read_uncommited_accessor =
+      this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
   EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1);
 
   mgp_graph immutable_graph = this->CreateGraph(memgraph::storage::View::OLD);
@@ -326,7 +332,8 @@ TYPED_TEST(MgpGraphTest, VertexSetProperty) {
     ASSERT_TRUE(result.HasValue());
     ASSERT_FALSE(accessor.Commit().HasError());
   }
-  auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+  auto read_uncommited_accessor =
+      this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
   EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1);
 
   mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW);
@@ -396,7 +403,8 @@ TYPED_TEST(MgpGraphTest, VertexAddLabel) {
   auto check_label = [&]() {
     EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0);
 
-    auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+    auto read_uncommited_accessor =
+        this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
     const auto maybe_vertex = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW);
     ASSERT_TRUE(maybe_vertex);
     const auto label_ids = maybe_vertex->Labels(memgraph::storage::View::NEW);
@@ -433,7 +441,8 @@ TYPED_TEST(MgpGraphTest, VertexRemoveLabel) {
   auto check_label = [&]() {
     EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0);
 
-    auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+    auto read_uncommited_accessor =
+        this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
     const auto maybe_vertex = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW);
     ASSERT_TRUE(maybe_vertex);
     const auto label_ids = maybe_vertex->Labels(memgraph::storage::View::NEW);
@@ -622,14 +631,15 @@ TYPED_TEST(MgpGraphTest, EdgeSetProperty) {
   {
     const auto vertex_ids = this->CreateEdge();
     from_vertex_id = vertex_ids[0];
-    auto accessor = this->storage->Access(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
+    auto accessor = this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
     auto edge = get_edge(accessor.get());
     const auto result =
         edge.SetProperty(accessor->NameToProperty(property_to_update), memgraph::storage::PropertyValue(42));
     ASSERT_TRUE(result.HasValue());
     ASSERT_FALSE(accessor->Commit().HasError());
   }
-  auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
+  auto read_uncommited_accessor =
+      this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
 
   mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW);
   MgpEdgePtr edge;
diff --git a/tests/unit/query_semantic.cpp b/tests/unit/query_semantic.cpp
index 5d015f103..c4bb966eb 100644
--- a/tests/unit/query_semantic.cpp
+++ b/tests/unit/query_semantic.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -34,7 +34,8 @@ class TestSymbolGenerator : public ::testing::Test {
   const std::string testSuite = "query_semantic";
   memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{
+      db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
   AstStorage storage;
 
diff --git a/tests/unit/query_trigger.cpp b/tests/unit/query_trigger.cpp
index 0eb9602c3..1b2ca5e9c 100644
--- a/tests/unit/query_trigger.cpp
+++ b/tests/unit/query_trigger.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -29,6 +29,8 @@
 #include "utils/exceptions.hpp"
 #include "utils/memory.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 namespace {
 const std::unordered_set<memgraph::query::TriggerEventType> kAllEventTypes{
     memgraph::query::TriggerEventType::ANY,           memgraph::query::TriggerEventType::VERTEX_CREATE,
@@ -69,7 +71,7 @@ class TriggerContextTest : public ::testing::Test {
   }
 
   memgraph::storage::Storage::Accessor *StartTransaction() {
-    accessors.emplace_back(db->Access());
+    accessors.emplace_back(db->Access(ReplicationRole::MAIN));
     return accessors.back().get();
   }
 
@@ -902,7 +904,7 @@ class TriggerStoreTest : public ::testing::Test {
 
     config = disk_test_utils::GenerateOnDiskConfig(testSuite);
     storage = std::make_unique<StorageType>(config);
-    storage_accessor = storage->Access();
+    storage_accessor = storage->Access(ReplicationRole::MAIN);
     dba.emplace(storage_accessor.get());
   }
 
diff --git a/tests/unit/query_variable_start_planner.cpp b/tests/unit/query_variable_start_planner.cpp
index b9772c2d3..df7173db2 100644
--- a/tests/unit/query_variable_start_planner.cpp
+++ b/tests/unit/query_variable_start_planner.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -28,6 +28,7 @@
 
 #include "formatters.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using namespace memgraph::query::plan;
 using memgraph::query::AstStorage;
 using Type = memgraph::query::EdgeAtom::Type;
@@ -110,7 +111,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra
 TYPED_TEST_CASE(TestVariableStartPlanner, StorageTypes);
 
 TYPED_TEST(TestVariableStartPlanner, MatchReturn) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Make a graph (v1) -[:r]-> (v2)
   auto v1 = dba.InsertVertex();
@@ -127,7 +128,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchReturn) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Make a graph (v1) -[:r]-> (v2) -[:r]-> (v3)
   auto v1 = dba.InsertVertex();
@@ -159,7 +160,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Make a graph (v1) -[:r]-> (v2) -[:r]-> (v3)
   auto v1 = dba.InsertVertex();
@@ -186,7 +187,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Graph (v1) -[:r]-> (v2)
   memgraph::query::VertexAccessor v1(dba.InsertVertex());
@@ -210,7 +211,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchWithMatchReturn) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Graph (v1) -[:r]-> (v2)
   memgraph::query::VertexAccessor v1(dba.InsertVertex());
@@ -230,7 +231,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchWithMatchReturn) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchVariableExpand) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   // Graph (v1) -[:r1]-> (v2) -[:r2]-> (v3)
   auto v1 = dba.InsertVertex();
@@ -253,7 +254,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchVariableExpand) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto id = dba.NameToProperty("id");
   // Graph (v1 {id:1}) -[:r1]-> (v2 {id: 2}) -[:r2]-> (v3 {id: 3})
@@ -281,7 +282,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto id = dba.NameToProperty("id");
   // Graph (v1 {id:1}) -[:r1]-> (v2) -[:r2]-> (v3)
@@ -307,7 +308,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, MatchBfs) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto id = dba.NameToProperty("id");
   // Graph (v1 {id:1}) -[:r1]-> (v2 {id: 2}) -[:r2]-> (v3 {id: 3})
@@ -334,7 +335,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchBfs) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, TestBasicSubquery) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto v1 = dba.InsertVertex();
@@ -356,7 +357,7 @@ TYPED_TEST(TestVariableStartPlanner, TestBasicSubquery) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
 
   auto v1 = dba.InsertVertex();
@@ -377,7 +378,7 @@ TYPED_TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithUnion) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto id = dba.NameToProperty("id");
 
@@ -405,7 +406,7 @@ TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithUnion) {
 }
 
 TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithTripleUnion) {
-  auto storage_dba = this->db->Access();
+  auto storage_dba = this->db->Access(ReplicationRole::MAIN);
   memgraph::query::DbAccessor dba(storage_dba.get());
   auto id = dba.NameToProperty("id");
 
diff --git a/tests/unit/replication_persistence_helper.cpp b/tests/unit/replication_persistence_helper.cpp
index ade9ef638..ef3ba254d 100644
--- a/tests/unit/replication_persistence_helper.cpp
+++ b/tests/unit/replication_persistence_helper.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,12 +22,7 @@
 
 using namespace memgraph::replication::durability;
 using namespace memgraph::replication;
-
-static_assert(sizeof(ReplicationRoleEntry) == 168,
-              "Most likely you modified ReplicationRoleEntry without updating the tests. ");
-
-static_assert(sizeof(ReplicationReplicaEntry) == 160,
-              "Most likely you modified ReplicationReplicaEntry without updating the tests.");
+using namespace memgraph::replication_coordination_glue;
 
 TEST(ReplicationDurability, V1Main) {
   auto const role_entry = ReplicationRoleEntry{.version = DurabilityVersion::V1,
diff --git a/tests/unit/skip_list.cpp b/tests/unit/skip_list.cpp
index 86fb4ae57..bdebacc1e 100644
--- a/tests/unit/skip_list.cpp
+++ b/tests/unit/skip_list.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -18,6 +18,20 @@
 #include "utils/skip_list.hpp"
 #include "utils/timer.hpp"
 
+#include <iterator>
+
+template <typename It, typename ConstIt>
+concept CompatibleIterators = std::forward_iterator<It> && std::forward_iterator<ConstIt> &&
+    requires(It it, ConstIt cit) {
+  { it == cit } -> std::same_as<bool>;
+  { it != cit } -> std::same_as<bool>;
+  { cit == it } -> std::same_as<bool>;
+  { cit != it } -> std::same_as<bool>;
+};
+
+using sut_t = memgraph::utils::SkipList<int64_t>::Accessor;
+static_assert(CompatibleIterators<sut_t::iterator, sut_t::const_iterator>);
+
 TEST(SkipList, Int) {
   memgraph::utils::SkipList<int64_t> list;
   {
diff --git a/tests/unit/slk_advanced.cpp b/tests/unit/slk_advanced.cpp
index fab936fe0..f41946388 100644
--- a/tests/unit/slk_advanced.cpp
+++ b/tests/unit/slk_advanced.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,10 +11,13 @@
 
 #include <gtest/gtest.h>
 
+#include "coordination/coordinator_config.hpp"
+#include "coordination/coordinator_slk.hpp"
+#include "replication/config.hpp"
+#include "replication_coordination_glue/mode.hpp"
+#include "slk_common.hpp"
 #include "storage/v2/property_value.hpp"
 #include "storage/v2/replication/slk.hpp"
-
-#include "slk_common.hpp"
 #include "storage/v2/temporal.hpp"
 
 TEST(SlkAdvanced, PropertyValueList) {
@@ -114,3 +117,34 @@ TEST(SlkAdvanced, PropertyValueComplex) {
 
   ASSERT_EQ(original, decoded);
 }
+
+TEST(SlkAdvanced, ReplicationClientConfigs) {
+  using ReplicationClientInfo = memgraph::coordination::CoordinatorClientConfig::ReplicationClientInfo;
+  using ReplicationClientInfoVec = std::vector<ReplicationClientInfo>;
+  using ReplicationMode = memgraph::replication_coordination_glue::ReplicationMode;
+
+  ReplicationClientInfoVec original{ReplicationClientInfo{.instance_name = "replica1",
+                                                          .replication_mode = ReplicationMode::SYNC,
+                                                          .replication_ip_address = "127.0.0.1",
+                                                          .replication_port = 10000},
+                                    ReplicationClientInfo{.instance_name = "replica2",
+                                                          .replication_mode = ReplicationMode::ASYNC,
+                                                          .replication_ip_address = "127.0.1.1",
+                                                          .replication_port = 10010},
+                                    ReplicationClientInfo{
+                                        .instance_name = "replica3",
+                                        .replication_mode = ReplicationMode::ASYNC,
+                                        .replication_ip_address = "127.1.1.1",
+                                        .replication_port = 1110,
+                                    }};
+
+  memgraph::slk::Loopback loopback;
+  auto builder = loopback.GetBuilder();
+  memgraph::slk::Save(original, builder);
+
+  ReplicationClientInfoVec decoded;
+  auto reader = loopback.GetReader();
+  memgraph::slk::Load(&decoded, reader);
+
+  ASSERT_EQ(original, decoded);
+}
diff --git a/tests/unit/storage_rocks.cpp b/tests/unit/storage_rocks.cpp
index 6d5db7d75..5cdaf4691 100644
--- a/tests/unit/storage_rocks.cpp
+++ b/tests/unit/storage_rocks.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -30,6 +30,8 @@
 #include "storage/v2/view.hpp"
 #include "utils/rocksdb_serialization.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 // NOLINTNEXTLINE(google-build-using-namespace)
 using namespace memgraph::storage;
 
@@ -57,14 +59,14 @@ class RocksDBStorageTest : public ::testing::TestWithParam<bool> {
 };
 
 TEST_F(RocksDBStorageTest, SerializeVertexGID) {
-  auto acc = storage->Access();
+  auto acc = storage->Access(ReplicationRole::MAIN);
   auto vertex = acc->CreateVertex();
   auto gid = vertex.Gid();
   ASSERT_EQ(memgraph::utils::SerializeVertex(*vertex.vertex_), "|" + gid.ToString());
 }
 
 TEST_F(RocksDBStorageTest, SerializeVertexGIDLabels) {
-  auto acc = storage->Access();
+  auto acc = storage->Access(ReplicationRole::MAIN);
   auto vertex = acc->CreateVertex();
   auto ser_player_label = acc->NameToLabel("Player");
   auto ser_user_label = acc->NameToLabel("User");
diff --git a/tests/unit/storage_v2.cpp b/tests/unit/storage_v2.cpp
index 74ac5b3fa..7db51ddd4 100644
--- a/tests/unit/storage_v2.cpp
+++ b/tests/unit/storage_v2.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,6 +23,8 @@
 #include "storage/v2/vertex_accessor.hpp"
 #include "storage_test_utils.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 using testing::Types;
 using testing::UnorderedElementsAre;
 
@@ -53,7 +55,7 @@ TYPED_TEST_CASE(StorageV2Test, StorageTypes);
 TYPED_TEST(StorageV2Test, Commit) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -63,7 +65,7 @@ TYPED_TEST(StorageV2Test, Commit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
     EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U);
     ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value());
@@ -71,7 +73,7 @@ TYPED_TEST(StorageV2Test, Commit) {
     acc->Abort();
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -87,7 +89,7 @@ TYPED_TEST(StorageV2Test, Commit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
     EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U);
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value());
@@ -100,7 +102,7 @@ TYPED_TEST(StorageV2Test, Commit) {
 TYPED_TEST(StorageV2Test, Abort) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -110,7 +112,7 @@ TYPED_TEST(StorageV2Test, Abort) {
     acc->Abort();
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
     EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U);
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value());
@@ -124,7 +126,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandCommit) {
   memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
 
     auto vertex1 = acc->CreateVertex();
     gid1 = vertex1.Gid();
@@ -148,7 +150,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value());
     ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value());
     ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value());
@@ -164,7 +166,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandAbort) {
   memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
 
     auto vertex1 = acc->CreateVertex();
     gid1 = vertex1.Gid();
@@ -188,7 +190,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandAbort) {
     acc->Abort();
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value());
     ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value());
     ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value());
@@ -201,8 +203,8 @@ TYPED_TEST(StorageV2Test, AdvanceCommandAbort) {
 
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(StorageV2Test, SnapshotIsolation) {
-  auto acc1 = this->store->Access();
-  auto acc2 = this->store->Access();
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
   auto vertex = acc1->CreateVertex();
   auto gid = vertex.Gid();
@@ -223,7 +225,7 @@ TYPED_TEST(StorageV2Test, SnapshotIsolation) {
 
   acc2->Abort();
 
-  auto acc3 = this->store->Access();
+  auto acc3 = this->store->Access(ReplicationRole::MAIN);
   ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value());
   EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U);
   ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value());
@@ -235,7 +237,7 @@ TYPED_TEST(StorageV2Test, SnapshotIsolation) {
 TYPED_TEST(StorageV2Test, AccessorMove) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
 
@@ -254,7 +256,7 @@ TYPED_TEST(StorageV2Test, AccessorMove) {
     ASSERT_FALSE(moved->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
     EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U);
     ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value());
@@ -266,8 +268,8 @@ TYPED_TEST(StorageV2Test, AccessorMove) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(StorageV2Test, VertexDeleteCommit) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-  auto acc1 = this->store->Access();  // read transaction
-  auto acc2 = this->store->Access();  // write transaction
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);  // read transaction
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);  // write transaction
 
   // Create the vertex in transaction 2
   {
@@ -280,8 +282,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteCommit) {
     ASSERT_FALSE(acc2->Commit().HasError());
   }
 
-  auto acc3 = this->store->Access();  // read transaction
-  auto acc4 = this->store->Access();  // write transaction
+  auto acc3 = this->store->Access(ReplicationRole::MAIN);  // read transaction
+  auto acc4 = this->store->Access(ReplicationRole::MAIN);  // write transaction
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -314,7 +316,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteCommit) {
     ASSERT_FALSE(acc4->Commit().HasError());
   }
 
-  auto acc5 = this->store->Access();  // read transaction
+  auto acc5 = this->store->Access(ReplicationRole::MAIN);  // read transaction
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -339,8 +341,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteCommit) {
 TYPED_TEST(StorageV2Test, VertexDeleteAbort) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
-  auto acc1 = this->store->Access();  // read transaction
-  auto acc2 = this->store->Access();  // write transaction
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);  // read transaction
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);  // write transaction
 
   // Create the vertex in transaction 2
   {
@@ -353,8 +355,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteAbort) {
     ASSERT_FALSE(acc2->Commit().HasError());
   }
 
-  auto acc3 = this->store->Access();  // read transaction
-  auto acc4 = this->store->Access();  // write transaction (aborted)
+  auto acc3 = this->store->Access(ReplicationRole::MAIN);  // read transaction
+  auto acc4 = this->store->Access(ReplicationRole::MAIN);  // write transaction (aborted)
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -387,8 +389,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteAbort) {
     acc4->Abort();
   }
 
-  auto acc5 = this->store->Access();  // read transaction
-  auto acc6 = this->store->Access();  // write transaction
+  auto acc5 = this->store->Access(ReplicationRole::MAIN);  // read transaction
+  auto acc6 = this->store->Access(ReplicationRole::MAIN);  // write transaction
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -427,7 +429,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteAbort) {
     ASSERT_FALSE(acc6->Commit().HasError());
   }
 
-  auto acc7 = this->store->Access();  // read transaction
+  auto acc7 = this->store->Access(ReplicationRole::MAIN);  // read transaction
 
   // Check whether the vertex exists in transaction 1
   ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -466,14 +468,14 @@ TYPED_TEST(StorageV2Test, VertexDeleteSerializationError) {
 
   // Create vertex
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
   }
 
-  auto acc1 = this->store->Access();
-  auto acc2 = this->store->Access();
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
   // Delete vertex in accessor 1
   {
@@ -546,7 +548,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSerializationError) {
 
   // Check whether the vertex exists
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_FALSE(vertex);
     EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U);
@@ -563,7 +565,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) {
   // Create vertex and delete it in the same transaction, but abort the
   // transaction
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid1 = vertex.Gid();
     ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value());
@@ -583,7 +585,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) {
 
   // Create vertex and delete it in the same transaction
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid2 = vertex.Gid();
     ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value());
@@ -603,7 +605,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) {
 
   // Check whether the vertices exist
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value());
     ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value());
     ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value());
@@ -620,7 +622,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteLabel) {
 
   // Create the vertex
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -630,7 +632,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteLabel) {
 
   // Add label, delete the vertex and check the label API (same command)
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -684,7 +686,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteLabel) {
 
   // Add label, delete the vertex and check the label API (different command)
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -775,7 +777,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteProperty) {
 
   // Create the vertex
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value());
@@ -785,7 +787,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteProperty) {
 
   // Set property, delete the vertex and check the property API (same command)
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -833,7 +835,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteProperty) {
   // Set property, delete the vertex and check the property API (different
   // command)
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -916,7 +918,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
 
@@ -948,7 +950,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) {
     spdlog::debug("Commit done");
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -977,7 +979,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) {
     spdlog::debug("Abort done");
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1009,7 +1011,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) {
     spdlog::debug("Commit done");
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1036,7 +1038,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Create the vertex.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -1044,7 +1046,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Add label 5, but abort the transaction.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1077,7 +1079,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Check that label 5 doesn't exist.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1098,7 +1100,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Add label 5.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1131,7 +1133,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Check that label 5 exists.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1161,7 +1163,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Remove label 5, but abort the transaction.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1194,7 +1196,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Check that label 5 exists.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1224,7 +1226,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Remove label 5.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1257,7 +1259,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 
   // Check that label 5 doesn't exist.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1281,14 +1283,14 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) {
 TYPED_TEST(StorageV2Test, VertexLabelSerializationError) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
   }
 
-  auto acc1 = this->store->Access();
-  auto acc2 = this->store->Access();
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
   // Add label 1 in accessor 1.
   {
@@ -1371,7 +1373,7 @@ TYPED_TEST(StorageV2Test, VertexLabelSerializationError) {
 
   // Check which labels exist.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1402,7 +1404,7 @@ TYPED_TEST(StorageV2Test, VertexLabelSerializationError) {
 TYPED_TEST(StorageV2Test, VertexPropertyCommit) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
 
@@ -1440,7 +1442,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1468,7 +1470,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyCommit) {
     acc->Abort();
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1499,7 +1501,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1525,7 +1527,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Create the vertex.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -1533,7 +1535,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Set property 5 to "nandare", but abort the transaction.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1573,7 +1575,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Check that property 5 is null.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1594,7 +1596,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Set property 5 to "nandare".
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1634,7 +1636,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Check that property 5 is "nandare".
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1664,7 +1666,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Set property 5 to null, but abort the transaction.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1705,7 +1707,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Check that property 5 is "nandare".
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1735,7 +1737,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Set property 5 to null.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1776,7 +1778,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 
   // Check that property 5 is null.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1800,14 +1802,14 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) {
 TYPED_TEST(StorageV2Test, VertexPropertySerializationError) {
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
   }
 
-  auto acc1 = this->store->Access();
-  auto acc2 = this->store->Access();
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
   // Set property 1 to 123 in accessor 1.
   {
@@ -1884,7 +1886,7 @@ TYPED_TEST(StorageV2Test, VertexPropertySerializationError) {
 
   // Check which properties exist.
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -1913,7 +1915,7 @@ TYPED_TEST(StorageV2Test, VertexPropertySerializationError) {
 
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(StorageV2Test, VertexLabelPropertyMixed) {
-  auto acc = this->store->Access();
+  auto acc = this->store->Access(ReplicationRole::MAIN);
   auto vertex = acc->CreateVertex();
 
   auto label = acc->NameToLabel("label5");
@@ -2155,7 +2157,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) {
   auto property1 = this->store->NameToProperty("property1");
   auto property2 = this->store->NameToProperty("property2");
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
 
@@ -2166,7 +2168,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -2198,7 +2200,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) {
     acc->Abort();
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -2209,7 +2211,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -2242,7 +2244,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -2258,7 +2260,7 @@ TYPED_TEST(StorageV2Test, VertexNonexistentLabelPropertyEdgeAPI) {
   auto label = this->store->NameToLabel("label");
   auto property = this->store->NameToProperty("property");
 
-  auto acc = this->store->Access();
+  auto acc = this->store->Access(ReplicationRole::MAIN);
   auto vertex = acc->CreateVertex();
 
   // Check state before (OLD view).
@@ -2314,8 +2316,8 @@ TYPED_TEST(StorageV2Test, VertexNonexistentLabelPropertyEdgeAPI) {
 }
 
 TYPED_TEST(StorageV2Test, VertexVisibilitySingleTransaction) {
-  auto acc1 = this->store->Access();
-  auto acc2 = this->store->Access();
+  auto acc1 = this->store->Access(ReplicationRole::MAIN);
+  auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
   auto vertex = acc1->CreateVertex();
   auto gid = vertex.Gid();
@@ -2334,7 +2336,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilitySingleTransaction) {
 
   ASSERT_TRUE(vertex.SetProperty(acc1->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue());
 
-  auto acc3 = this->store->Access();
+  auto acc3 = this->store->Access(ReplicationRole::MAIN);
 
   EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD));
   EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW));
@@ -2371,8 +2373,8 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
   memgraph::storage::Gid gid;
 
   {
-    auto acc1 = this->store->Access();
-    auto acc2 = this->store->Access();
+    auto acc1 = this->store->Access(ReplicationRole::MAIN);
+    auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
     auto vertex = acc1->CreateVertex();
     gid = vertex.Gid();
@@ -2401,8 +2403,8 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
   }
 
   {
-    auto acc1 = this->store->Access();
-    auto acc2 = this->store->Access();
+    auto acc1 = this->store->Access(ReplicationRole::MAIN);
+    auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
     auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
@@ -2435,7 +2437,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
 
     ASSERT_TRUE(vertex->SetProperty(acc1->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue());
 
-    auto acc3 = this->store->Access();
+    auto acc3 = this->store->Access(ReplicationRole::MAIN);
 
     EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD));
     EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW));
@@ -2477,15 +2479,15 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
   }
 
   {
-    auto acc1 = this->store->Access();
-    auto acc2 = this->store->Access();
+    auto acc1 = this->store->Access(ReplicationRole::MAIN);
+    auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
     auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
     ASSERT_TRUE(acc1->DeleteVertex(&*vertex).HasValue());
 
-    auto acc3 = this->store->Access();
+    auto acc3 = this->store->Access(ReplicationRole::MAIN);
 
     EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD));
     EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW));
@@ -2527,7 +2529,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
   }
 
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
 
     EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD));
     EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW));
@@ -2541,15 +2543,15 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
   }
 
   {
-    auto acc1 = this->store->Access();
-    auto acc2 = this->store->Access();
+    auto acc1 = this->store->Access(ReplicationRole::MAIN);
+    auto acc2 = this->store->Access(ReplicationRole::MAIN);
 
     auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
     ASSERT_TRUE(acc1->DeleteVertex(&*vertex).HasValue());
 
-    auto acc3 = this->store->Access();
+    auto acc3 = this->store->Access(ReplicationRole::MAIN);
 
     EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD));
     EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW));
@@ -2591,7 +2593,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) {
   }
 
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
 
     EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD));
     EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW));
@@ -2613,14 +2615,14 @@ TYPED_TEST(StorageV2Test, DeletedVertexAccessor) {
   std::optional<memgraph::storage::Gid> gid;
   // Create the vertex
   {
-    auto acc = this->store->Access();
+    auto acc = this->store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_FALSE(vertex.SetProperty(property, property_value).HasError());
     ASSERT_FALSE(acc->Commit().HasError());
   }
 
-  auto acc = this->store->Access();
+  auto acc = this->store->Access(ReplicationRole::MAIN);
   auto vertex = acc->FindVertex(*gid, memgraph::storage::View::OLD);
   ASSERT_TRUE(vertex);
   auto maybe_deleted_vertex = acc->DeleteVertex(&*vertex);
diff --git a/tests/unit/storage_v2_constraints.cpp b/tests/unit/storage_v2_constraints.cpp
index dfee45a0e..7f03f40d1 100644
--- a/tests/unit/storage_v2_constraints.cpp
+++ b/tests/unit/storage_v2_constraints.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,6 +23,8 @@
 
 #include "disk_test_utils.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
+
 // NOLINTNEXTLINE(google-build-using-namespace)
 using namespace memgraph::storage;
 
@@ -81,7 +83,7 @@ TYPED_TEST_CASE(ConstraintsTest, StorageTypes);
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_EQ(acc->ListAllConstraints().existence.size(), 0);
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -92,7 +94,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label1, this->prop1)));
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -103,7 +105,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label1, this->prop1)));
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -114,7 +116,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label1, this->prop1),
                                                                           std::make_pair(this->label2, this->prop1)));
     ASSERT_NO_ERROR(acc->Commit());
@@ -130,7 +132,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label2, this->prop1)));
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -145,7 +147,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_EQ(acc->ListAllConstraints().existence.size(), 0);
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -156,7 +158,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label2, this->prop1)));
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -165,7 +167,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(acc->Commit());
@@ -180,7 +182,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) {
     ASSERT_FALSE(unique_acc->Commit().HasError());  // TODO: Check if we are committing here?
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (auto vertex : acc->Vertices(View::OLD)) {
       ASSERT_NO_ERROR(acc->DeleteVertex(&vertex));
     }
@@ -197,7 +199,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure2) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(acc->Commit());
@@ -212,7 +214,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure2) {
     ASSERT_FALSE(unique_acc->Commit().HasError());  // TODO: Check if we are committing here?
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (auto vertex : acc->Vertices(View::OLD)) {
       ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1)));
     }
@@ -236,7 +238,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
 
@@ -248,7 +250,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1)));
@@ -256,7 +258,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (auto vertex : acc->Vertices(View::OLD)) {
       ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue()));
     }
@@ -269,7 +271,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (auto vertex : acc->Vertices(View::OLD)) {
       ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue()));
     }
@@ -285,7 +287,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(acc->Commit());
@@ -295,7 +297,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_EQ(acc->ListAllConstraints().unique.size(), 0);
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -307,7 +309,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().unique,
                 UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1})));
     ASSERT_NO_ERROR(acc->Commit());
@@ -320,7 +322,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().unique,
                 UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1})));
     ASSERT_NO_ERROR(acc->Commit());
@@ -333,7 +335,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().unique,
                 UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}),
                                      std::make_pair(this->label2, std::set<PropertyId>{this->prop1})));
@@ -352,7 +354,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().unique,
                 UnorderedElementsAre(std::make_pair(this->label2, std::set<PropertyId>{this->prop1})));
     ASSERT_NO_ERROR(acc->Commit());
@@ -370,7 +372,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_EQ(acc->ListAllConstraints().unique.size(), 0);
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -381,7 +383,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
     EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS);
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().unique,
                 UnorderedElementsAre(std::make_pair(this->label2, std::set<PropertyId>{this->prop1})));
     ASSERT_NO_ERROR(acc->Commit());
@@ -391,7 +393,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 2; ++i) {
       auto vertex1 = acc->CreateVertex();
       ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
@@ -411,7 +413,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (auto vertex : acc->Vertices(View::OLD)) {
       ASSERT_NO_ERROR(acc->DeleteVertex(&vertex));
     }
@@ -430,7 +432,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure2) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 2; ++i) {
       auto vertex = acc->CreateVertex();
       ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
@@ -450,7 +452,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure2) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     int value = 0;
     for (auto vertex : acc->Vertices(View::OLD)) {
       ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(value)));
@@ -473,7 +475,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) {
   Gid gid1;
   Gid gid2;
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
     gid1 = vertex1.Gid();
@@ -493,7 +495,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid1, View::OLD);
     auto vertex2 = acc->FindVertex(gid2, View::OLD);
 
@@ -505,7 +507,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid1, View::OLD);
     auto vertex2 = acc->FindVertex(gid2, View::OLD);
     ASSERT_NO_ERROR(vertex1->SetProperty(this->prop1, PropertyValue(2)));
@@ -528,8 +530,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation2) {
     // tx1: B---SP(v1, 1)---SP(v1, 2)---OK--
     // tx2: -B---SP(v2, 2)---SP(v2, 1)---OK-
 
-    auto acc1 = this->storage->Access();
-    auto acc2 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->CreateVertex();
     auto vertex2 = acc2->CreateVertex();
 
@@ -561,7 +563,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation3) {
     // tx2: --------------------B---SP(v1, 2)---OK--
     // tx3: ---------------------B---SP(v2, 1)---OK-
 
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->CreateVertex();
     auto gid = vertex1.Gid();
 
@@ -570,8 +572,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation3) {
 
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
-    auto acc3 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc2->FindVertex(gid, View::NEW);  // vertex1 == vertex2
     auto vertex3 = acc3->CreateVertex();
 
@@ -599,7 +601,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation4) {
     // tx2: --------------------B---SP(v2, 1)-----OK-
     // tx3: ---------------------B---SP(v1, 2)---OK--
 
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->CreateVertex();
     auto gid = vertex1.Gid();
 
@@ -607,8 +609,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation4) {
 
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
-    auto acc3 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc2->CreateVertex();
     auto vertex3 = acc3->FindVertex(gid, View::NEW);
 
@@ -632,7 +634,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit1) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
@@ -663,7 +665,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit2) {
     // tx2: -------------------------------B---SP(v1, 3)---OK----
     // tx3: --------------------------------B---SP(v2, 3)---FAIL-
 
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->CreateVertex();
     auto vertex2 = acc1->CreateVertex();
     auto gid1 = vertex1.Gid();
@@ -676,8 +678,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit2) {
 
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
-    auto acc3 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex3 = acc2->FindVertex(gid1, View::NEW);  // vertex3 == vertex1
     auto vertex4 = acc3->FindVertex(gid2, View::NEW);  // vertex4 == vertex2
 
@@ -709,7 +711,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit3) {
     // tx2: -------------------------------B---SP(v1, 2)---FAIL--
     // tx3: --------------------------------B---SP(v2, 1)---FAIL-
 
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->CreateVertex();
     auto vertex2 = acc1->CreateVertex();
     auto gid1 = vertex1.Gid();
@@ -722,8 +724,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit3) {
 
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
-    auto acc3 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex3 = acc2->FindVertex(gid1, View::OLD);  // vertex3 == vertex1
     auto vertex4 = acc3->FindVertex(gid2, View::OLD);  // vertex4 == vertex2
 
@@ -762,7 +764,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) {
   {
     // B---AL(v2)---SP(v1, 1)---SP(v2, 1)---OK
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
     gid1 = vertex1.Gid();
@@ -782,8 +784,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) {
     // tx1: B---AL(v1)-----OK-
     // tx2: -B---RL(v2)---OK--
 
-    auto acc1 = this->storage->Access();
-    auto acc2 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->FindVertex(gid1, View::OLD);
     auto vertex2 = acc2->FindVertex(gid2, View::OLD);
 
@@ -811,7 +813,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) {
   {
     // B---AL(v2)---FAIL
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc->FindVertex(gid2, View::OLD);
     ASSERT_NO_ERROR(vertex2->AddLabel(this->label1));
 
@@ -824,7 +826,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) {
   {
     // B---RL(v1)---OK
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid1, View::OLD);
     ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label1));
     ASSERT_NO_ERROR(acc->Commit());
@@ -834,8 +836,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) {
     // tx1: B---AL(v1)-----FAIL
     // tx2: -B---AL(v2)---OK---
 
-    auto acc1 = this->storage->Access();
-    auto acc2 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->FindVertex(gid1, View::OLD);
     auto vertex2 = acc2->FindVertex(gid2, View::OLD);
 
@@ -911,7 +913,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsPropertySetSize) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label1, properties)));
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -922,7 +924,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsPropertySetSize) {
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     ASSERT_TRUE(acc->ListAllConstraints().unique.empty());
     ASSERT_NO_ERROR(acc->Commit());
   }
@@ -951,7 +953,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) {
   Gid gid1;
   Gid gid2;
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
     gid1 = vertex1.Gid();
@@ -971,7 +973,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) {
   // Try to change property of the second vertex so it becomes the same as the
   // first vertex-> It should fail.
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc->FindVertex(gid2, View::OLD);
     ASSERT_NO_ERROR(vertex2->SetProperty(this->prop2, PropertyValue(2)));
     auto res = acc->Commit();
@@ -985,7 +987,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) {
   // both vertices should now be equal. However, this operation should succeed
   // since null value is treated as non-existing property.
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid1, View::OLD);
     auto vertex2 = acc->FindVertex(gid2, View::OLD);
     ASSERT_NO_ERROR(vertex1->SetProperty(this->prop2, PropertyValue()));
@@ -1005,7 +1007,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertAbortInsert) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1)));
@@ -1014,7 +1016,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertAbortInsert) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2)));
@@ -1034,7 +1036,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) {
 
   Gid gid;
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
@@ -1044,14 +1046,14 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, View::OLD);
     ASSERT_NO_ERROR(acc->DeleteVertex(&*vertex));
     ASSERT_NO_ERROR(acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1)));
@@ -1071,7 +1073,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) {
 
   Gid gid;
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
@@ -1081,14 +1083,14 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, View::OLD);
     ASSERT_NO_ERROR(acc->DeleteVertex(&*vertex));
     acc->Abort();
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(1)));
@@ -1114,7 +1116,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) {
   Gid gid1;
   Gid gid2;
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
     gid1 = vertex1.Gid();
@@ -1129,8 +1131,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) {
   }
 
   {
-    auto acc1 = this->storage->Access();
-    auto acc2 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = acc1->FindVertex(gid1, View::OLD);
     auto vertex2 = acc2->FindVertex(gid2, View::OLD);
 
@@ -1156,7 +1158,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertDropInsert) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1)));
@@ -1172,7 +1174,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertDropInsert) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2)));
@@ -1194,7 +1196,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2)));
@@ -1203,7 +1205,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1)));
@@ -1212,7 +1214,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(0)));
@@ -1238,7 +1240,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsClearOldData) {
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2)));
@@ -1246,14 +1248,14 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsClearOldData) {
 
     ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-    auto acc2 = this->storage->Access(std::nullopt);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value();
     ASSERT_TRUE(vertex2.SetProperty(this->prop1, memgraph::storage::PropertyValue(2)).HasValue());
     ASSERT_FALSE(acc2->Commit().HasError());
 
     ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-    auto acc3 = this->storage->Access(std::nullopt);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value();
     ASSERT_TRUE(vertex3.SetProperty(this->prop1, memgraph::storage::PropertyValue(10)).HasValue());
     ASSERT_FALSE(acc3->Commit().HasError());
diff --git a/tests/unit/storage_v2_durability_inmemory.cpp b/tests/unit/storage_v2_durability_inmemory.cpp
index 8a6d26fd1..54671077f 100644
--- a/tests/unit/storage_v2_durability_inmemory.cpp
+++ b/tests/unit/storage_v2_durability_inmemory.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -48,6 +48,7 @@
 #include "utils/timer.hpp"
 #include "utils/uuid.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using testing::Contains;
 using testing::UnorderedElementsAre;
 
@@ -93,26 +94,26 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
 
     {
       // Create label index.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateIndex(label_unindexed).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
       // Create label index statistics.
-      auto acc = store->Access();
+      auto acc = store->Access(ReplicationRole::MAIN);
       acc->SetIndexStats(label_unindexed, memgraph::storage::LabelIndexStats{1, 2});
       ASSERT_TRUE(acc->GetIndexStats(label_unindexed));
       ASSERT_FALSE(acc->Commit().HasError());
     }
     {
       // Create label+property index.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateIndex(label_indexed, property_id).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
       // Create label+property index statistics.
-      auto acc = store->Access();
+      auto acc = store->Access(ReplicationRole::MAIN);
       acc->SetIndexStats(label_indexed, property_id, memgraph::storage::LabelPropertyIndexStats{1, 2, 3.4, 5.6, 0.0});
       ASSERT_TRUE(acc->GetIndexStats(label_indexed, property_id));
       ASSERT_FALSE(acc->Commit().HasError());
@@ -120,20 +121,20 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
 
     {
       // Create existence constraint.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateExistenceConstraint(label_unindexed, property_id).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
       // Create unique constraint.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateUniqueConstraint(label_unindexed, {property_id, property_extra}).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
 
     // Create vertices.
     for (uint64_t i = 0; i < kNumBaseVertices; ++i) {
-      auto acc = store->Access();
+      auto acc = store->Access(ReplicationRole::MAIN);
       auto vertex = acc->CreateVertex();
       base_vertex_gids_[i] = vertex.Gid();
       if (i < kNumBaseVertices / 2) {
@@ -150,7 +151,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
 
     // Create edges.
     for (uint64_t i = 0; i < kNumBaseEdges; ++i) {
-      auto acc = store->Access();
+      auto acc = store->Access(ReplicationRole::MAIN);
       auto vertex1 = acc->FindVertex(base_vertex_gids_[(i / 2) % kNumBaseVertices], memgraph::storage::View::OLD);
       ASSERT_TRUE(vertex1);
       auto vertex2 = acc->FindVertex(base_vertex_gids_[(i / 3) % kNumBaseVertices], memgraph::storage::View::OLD);
@@ -186,26 +187,26 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
 
     {
       // Create label index.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateIndex(label_unused).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
       // Create label index statistics.
-      auto acc = store->Access();
+      auto acc = store->Access(ReplicationRole::MAIN);
       acc->SetIndexStats(label_unused, memgraph::storage::LabelIndexStats{123, 9.87});
       ASSERT_TRUE(acc->GetIndexStats(label_unused));
       ASSERT_FALSE(acc->Commit().HasError());
     }
     {
       // Create label+property index.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateIndex(label_indexed, property_count).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
       // Create label+property index statistics.
-      auto acc = store->Access();
+      auto acc = store->Access(ReplicationRole::MAIN);
       acc->SetIndexStats(label_indexed, property_count,
                          memgraph::storage::LabelPropertyIndexStats{456798, 312345, 12312312.2, 123123.2, 67876.9});
       ASSERT_TRUE(acc->GetIndexStats(label_indexed, property_count));
@@ -214,25 +215,25 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
 
     {
       // Create existence constraint.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateExistenceConstraint(label_unused, property_count).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
 
     {
       // Create unique constraint.
-      auto unique_acc = store->UniqueAccess();
+      auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateUniqueConstraint(label_unused, {property_count}).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
 
     // Storage accessor.
     std::unique_ptr<memgraph::storage::Storage::Accessor> acc;
-    if (single_transaction) acc = store->Access();
+    if (single_transaction) acc = store->Access(ReplicationRole::MAIN);
 
     // Create vertices.
     for (uint64_t i = 0; i < kNumExtendedVertices; ++i) {
-      if (!single_transaction) acc = store->Access();
+      if (!single_transaction) acc = store->Access(ReplicationRole::MAIN);
       auto vertex = acc->CreateVertex();
       extended_vertex_gids_[i] = vertex.Gid();
       if (i < kNumExtendedVertices / 2) {
@@ -246,7 +247,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
 
     // Create edges.
     for (uint64_t i = 0; i < kNumExtendedEdges; ++i) {
-      if (!single_transaction) acc = store->Access();
+      if (!single_transaction) acc = store->Access(ReplicationRole::MAIN);
       auto vertex1 =
           acc->FindVertex(extended_vertex_gids_[(i / 5) % kNumExtendedVertices], memgraph::storage::View::NEW);
       ASSERT_TRUE(vertex1);
@@ -285,7 +286,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> {
     auto et4 = store->NameToEdgeType("extended_et4");
 
     // Create storage accessor.
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
 
     // Verify indices info.
     {
@@ -802,8 +803,8 @@ INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, DurabilityTest, ::testing::Value
 TEST_P(DurabilityTest, SnapshotOnExit) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{.durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+                                     .salient = {.items = {.properties_on_edges = GetParam()}}};
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -818,15 +819,17 @@ TEST_P(DurabilityTest, SnapshotOnExit) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -839,10 +842,12 @@ TEST_P(DurabilityTest, SnapshotPeriodic) {
   // Create snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
+
         .durability = {.storage_directory = storage_directory,
                        .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT,
-                       .snapshot_interval = std::chrono::milliseconds(2000)}};
+                       .snapshot_interval = std::chrono::milliseconds(2000)},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -855,15 +860,17 @@ TEST_P(DurabilityTest, SnapshotPeriodic) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -881,13 +888,16 @@ TEST_P(DurabilityTest, SnapshotFallback) {
     auto const snapshot_interval = std::chrono::milliseconds(3000);
 
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT,
-            .snapshot_interval = snapshot_interval,
-            .snapshot_retention_count = 10,  // We don't anticipate that we make this many
-        }};
+
+        .durability =
+            {
+                .storage_directory = storage_directory,
+                .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT,
+                .snapshot_interval = snapshot_interval,
+                .snapshot_retention_count = 10,  // We don't anticipate that we make this many
+            },
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
 
@@ -925,15 +935,17 @@ TEST_P(DurabilityTest, SnapshotFallback) {
   }
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -945,12 +957,14 @@ TEST_P(DurabilityTest, SnapshotFallback) {
 TEST_P(DurabilityTest, SnapshotEverythingCorrupt) {
   // Create unrelated snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
 
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -974,10 +988,12 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) {
   // Create snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
+
         .durability = {.storage_directory = storage_directory,
                        .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT,
-                       .snapshot_interval = std::chrono::milliseconds(2000)}};
+                       .snapshot_interval = std::chrono::milliseconds(2000)},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
 
@@ -1018,8 +1034,10 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) {
   ASSERT_DEATH(
       ([&]() {
         memgraph::storage::Config config{
-            .items = {.properties_on_edges = GetParam()},
-            .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+            .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+            .salient = {.items = {.properties_on_edges = GetParam()}},
+        };
         memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
         memgraph::dbms::Database db{config, repl_state};
       }())  // iile
@@ -1031,11 +1049,13 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) {
 TEST_P(DurabilityTest, SnapshotRetention) {
   // Create unrelated snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -1050,11 +1070,13 @@ TEST_P(DurabilityTest, SnapshotRetention) {
   // Create snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
+
         .durability = {.storage_directory = storage_directory,
                        .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT,
                        .snapshot_interval = std::chrono::milliseconds(2000),
-                       .snapshot_retention_count = 3}};
+                       .snapshot_retention_count = 3},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     // Restore unrelated snapshots after the database has been started.
@@ -1088,15 +1110,17 @@ TEST_P(DurabilityTest, SnapshotRetention) {
   }
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1108,8 +1132,10 @@ TEST_P(DurabilityTest, SnapshotRetention) {
 TEST_P(DurabilityTest, SnapshotMixedUUID) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -1126,8 +1152,10 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) {
   // Recover snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+        .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
@@ -1135,8 +1163,10 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) {
 
   // Create another snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -1157,15 +1187,17 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1177,11 +1209,13 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) {
 TEST_P(DurabilityTest, SnapshotBackup) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -1196,10 +1230,12 @@ TEST_P(DurabilityTest, SnapshotBackup) {
   // Start storage without recovery.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
+
         .durability = {.storage_directory = storage_directory,
                        .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT,
-                       .snapshot_interval = std::chrono::minutes(20)}};
+                       .snapshot_interval = std::chrono::minutes(20)},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
   }
@@ -1214,8 +1250,10 @@ TEST_P(DurabilityTest, SnapshotBackup) {
 TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnEdges) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = false},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = false}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), false);
@@ -1230,15 +1268,17 @@ TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnE
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = true},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = true}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, false);
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1250,8 +1290,10 @@ TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnE
 TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnEdges) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = true},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = true}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), true);
@@ -1269,8 +1311,10 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnE
   ASSERT_DEATH(
       ([&]() {
         memgraph::storage::Config config{
-            .items = {.properties_on_edges = false},
-            .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+            .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+            .salient = {.items = {.properties_on_edges = false}},
+        };
         memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
         memgraph::dbms::Database db{config, repl_state};
       }())  // iile
@@ -1282,8 +1326,10 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnE
 TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutPropertiesOnEdges) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = true},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = true}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), true);
@@ -1292,7 +1338,7 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp
     VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, true);
     // Remove properties from edges.
     {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) {
         auto in_edges = vertex.InEdges(memgraph::storage::View::OLD);
         ASSERT_TRUE(in_edges.HasValue());
@@ -1325,15 +1371,17 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = false},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = false}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, false);
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1346,12 +1394,14 @@ TEST_P(DurabilityTest, WalBasic) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -1364,15 +1414,17 @@ TEST_P(DurabilityTest, WalBasic) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1385,16 +1437,18 @@ TEST_P(DurabilityTest, WalBackup) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -1410,11 +1464,13 @@ TEST_P(DurabilityTest, WalBackup) {
   // Start storage without recovery.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20)}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20)},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
   }
@@ -1430,12 +1486,14 @@ TEST_P(DurabilityTest, WalAppendToExisting) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -1449,8 +1507,10 @@ TEST_P(DurabilityTest, WalAppendToExisting) {
   // Recover WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+        .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
@@ -1459,13 +1519,15 @@ TEST_P(DurabilityTest, WalAppendToExisting) {
   // Recover WALs and create more WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .recover_on_startup = true,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .recover_on_startup = true,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateExtendedDataset(db.storage());
@@ -1477,15 +1539,17 @@ TEST_P(DurabilityTest, WalAppendToExisting) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1501,15 +1565,17 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto v1 = acc->CreateVertex();
     gid_v1 = v1.Gid();
     auto v2 = acc->CreateVertex();
@@ -1540,12 +1606,14 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
 
     auto indices = acc->ListAllIndices();
     ASSERT_EQ(indices.label.size(), 0);
@@ -1628,7 +1696,7 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1641,50 +1709,52 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
     CreateExtendedDataset(db.storage());
     auto indices = [&] {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto res = acc->ListAllIndices();
-      acc->Commit();
+      (void)acc->Commit();
       return res;
     }();  // iile
     for (const auto &index : indices.label) {
-      auto unique_acc = db.storage()->UniqueAccess();
+      auto unique_acc = db.UniqueAccess();
       ASSERT_FALSE(unique_acc->DropIndex(index).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     for (const auto &index : indices.label_property) {
-      auto unique_acc = db.storage()->UniqueAccess();
+      auto unique_acc = db.UniqueAccess();
       ASSERT_FALSE(unique_acc->DropIndex(index.first, index.second).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     auto constraints = [&] {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto res = acc->ListAllConstraints();
-      acc->Commit();
+      (void)acc->Commit();
       return res;
     }();  // iile
     for (const auto &constraint : constraints.existence) {
-      auto unique_acc = db.storage()->UniqueAccess();
+      auto unique_acc = db.UniqueAccess();
       ASSERT_FALSE(unique_acc->DropExistenceConstraint(constraint.first, constraint.second).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     for (const auto &constraint : constraints.unique) {
-      auto unique_acc = db.storage()->UniqueAccess();
+      auto unique_acc = db.UniqueAccess();
       ASSERT_EQ(unique_acc->DropUniqueConstraint(constraint.first, constraint.second),
                 memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS);
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) {
       ASSERT_TRUE(acc->DetachDeleteVertex(&vertex).HasValue());
     }
@@ -1697,12 +1767,14 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto indices = acc->ListAllIndices();
     ASSERT_EQ(indices.label.size(), 0);
     ASSERT_EQ(indices.label_property.size(), 0);
@@ -1719,7 +1791,7 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1735,18 +1807,21 @@ TEST_P(DurabilityTest, WalTransactionOrdering) {
   // Create WAL.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 100000,
-            .wal_file_flush_every_n_tx = kFlushWalEvery,
-        }};
+
+        .durability =
+            {
+                .storage_directory = storage_directory,
+                .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                .snapshot_interval = std::chrono::minutes(20),
+                .wal_file_size_kibibytes = 100000,
+                .wal_file_flush_every_n_tx = kFlushWalEvery,
+            },
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc1 = db.storage()->Access();
-    auto acc2 = db.storage()->Access();
+    auto acc1 = db.Access();
+    auto acc2 = db.Access();
 
     // Create vertex in transaction 2.
     {
@@ -1756,7 +1831,7 @@ TEST_P(DurabilityTest, WalTransactionOrdering) {
           vertex2.SetProperty(db.storage()->NameToProperty("id"), memgraph::storage::PropertyValue(2)).HasValue());
     }
 
-    auto acc3 = db.storage()->Access();
+    auto acc3 = db.Access();
 
     // Create vertex in transaction 3.
     {
@@ -1834,12 +1909,14 @@ TEST_P(DurabilityTest, WalTransactionOrdering) {
   }
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (auto [gid, id] : std::vector<std::pair<memgraph::storage::Gid, int64_t>>{{gid1, 1}, {gid2, 2}, {gid3, 3}}) {
       auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
       ASSERT_TRUE(vertex);
@@ -1855,7 +1932,7 @@ TEST_P(DurabilityTest, WalTransactionOrdering) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1868,19 +1945,21 @@ TEST_P(DurabilityTest, WalCreateAndRemoveOnlyBaseDataset) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
     CreateExtendedDataset(db.storage());
     auto label_indexed = db.storage()->NameToLabel("base_indexed");
     auto label_unindexed = db.storage()->NameToLabel("base_unindexed");
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) {
       auto has_indexed = vertex.HasLabel(label_indexed, memgraph::storage::View::OLD);
       ASSERT_TRUE(has_indexed.HasValue());
@@ -1897,15 +1976,17 @@ TEST_P(DurabilityTest, WalCreateAndRemoveOnlyBaseDataset) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::ONLY_EXTENDED_WITH_BASE_INDICES_AND_CONSTRAINTS, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -1920,17 +2001,19 @@ TEST_P(DurabilityTest, WalDeathResilience) {
     // Create WALs.
     {
       memgraph::storage::Config config{
-          .items = {.properties_on_edges = GetParam()},
-          .durability = {
-              .storage_directory = storage_directory,
-              .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-              .snapshot_interval = std::chrono::minutes(20),
-              .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+          .durability = {.storage_directory = storage_directory,
+                         .snapshot_wal_mode =
+                             memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                         .snapshot_interval = std::chrono::minutes(20),
+                         .wal_file_flush_every_n_tx = kFlushWalEvery},
+          .salient = {.items = {.properties_on_edges = GetParam()}},
+      };
       memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
       memgraph::dbms::Database db{config, repl_state};
       // Create one million vertices.
       for (uint64_t i = 0; i < 1000000; ++i) {
-        auto acc = db.storage()->Access();
+        auto acc = db.Access();
         acc->CreateVertex();
         MG_ASSERT(!acc->Commit().HasError(), "Couldn't commit transaction!");
       }
@@ -1957,18 +2040,21 @@ TEST_P(DurabilityTest, WalDeathResilience) {
   uint64_t count = 0;
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .recover_on_startup = true,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery,
-        }};
+
+        .durability =
+            {
+                .storage_directory = storage_directory,
+                .recover_on_startup = true,
+                .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                .snapshot_interval = std::chrono::minutes(20),
+                .wal_file_flush_every_n_tx = kFlushWalEvery,
+            },
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto iterable = acc->Vertices(memgraph::storage::View::OLD);
       for (auto it = iterable.begin(); it != iterable.end(); ++it) {
         ++count;
@@ -1977,7 +2063,7 @@ TEST_P(DurabilityTest, WalDeathResilience) {
     }
 
     {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       for (uint64_t i = 0; i < kExtraItems; ++i) {
         acc->CreateVertex();
       }
@@ -1991,13 +2077,15 @@ TEST_P(DurabilityTest, WalDeathResilience) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   {
     uint64_t current = 0;
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto iterable = acc->Vertices(memgraph::storage::View::OLD);
     for (auto it = iterable.begin(); it != iterable.end(); ++it) {
       ++current;
@@ -2007,7 +2095,7 @@ TEST_P(DurabilityTest, WalDeathResilience) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2020,16 +2108,18 @@ TEST_P(DurabilityTest, WalMissingSecond) {
   // Create unrelated WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -2046,26 +2136,28 @@ TEST_P(DurabilityTest, WalMissingSecond) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     const uint64_t kNumVertices = 1000;
     std::vector<memgraph::storage::Gid> gids;
     gids.reserve(kNumVertices);
     for (uint64_t i = 0; i < kNumVertices; ++i) {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto vertex = acc->CreateVertex();
       gids.push_back(vertex.Gid());
       ASSERT_FALSE(acc->Commit().HasError());
     }
     for (uint64_t i = 0; i < kNumVertices; ++i) {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
       ASSERT_TRUE(vertex);
       ASSERT_TRUE(
@@ -2101,8 +2193,10 @@ TEST_P(DurabilityTest, WalMissingSecond) {
   ASSERT_DEATH(
       ([&]() {
         memgraph::storage::Config config{
-            .items = {.properties_on_edges = GetParam()},
-            .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+            .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+            .salient = {.items = {.properties_on_edges = GetParam()}},
+        };
         memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
         memgraph::dbms::Database db{config, repl_state};
       }())  // iile
@@ -2115,16 +2209,18 @@ TEST_P(DurabilityTest, WalCorruptSecond) {
   // Create unrelated WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -2141,26 +2237,28 @@ TEST_P(DurabilityTest, WalCorruptSecond) {
   // Create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     const uint64_t kNumVertices = 1000;
     std::vector<memgraph::storage::Gid> gids;
     gids.reserve(kNumVertices);
     for (uint64_t i = 0; i < kNumVertices; ++i) {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto vertex = acc->CreateVertex();
       gids.push_back(vertex.Gid());
       ASSERT_FALSE(acc->Commit().HasError());
     }
     for (uint64_t i = 0; i < kNumVertices; ++i) {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD);
       ASSERT_TRUE(vertex);
       ASSERT_TRUE(
@@ -2195,8 +2293,10 @@ TEST_P(DurabilityTest, WalCorruptSecond) {
   ASSERT_DEATH(
       ([&]() {
         memgraph::storage::Config config{
-            .items = {.properties_on_edges = GetParam()},
-            .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+            .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+            .salient = {.items = {.properties_on_edges = GetParam()}},
+        };
         memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
         memgraph::dbms::Database db{config, repl_state};
       }())  // iile
@@ -2209,13 +2309,15 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) {
   // Create WALs
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -2236,8 +2338,10 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) {
   }
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   // The extended dataset shouldn't be recovered because its WAL transaction was
@@ -2246,7 +2350,7 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2259,16 +2363,18 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) {
   // Create WALs
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
     ASSERT_TRUE(vertex1.AddLabel(acc->NameToLabel("nandare")).HasValue());
@@ -2309,12 +2415,14 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     uint64_t count = 0;
     auto iterable = acc->Vertices(memgraph::storage::View::OLD);
     for (auto it = iterable.begin(); it != iterable.end(); ++it) {
@@ -2325,7 +2433,7 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2338,12 +2446,14 @@ TEST_P(DurabilityTest, WalAndSnapshot) {
   // Create snapshot and WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::milliseconds(2000),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::milliseconds(2000),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -2357,15 +2467,17 @@ TEST_P(DurabilityTest, WalAndSnapshot) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot and WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2377,8 +2489,10 @@ TEST_P(DurabilityTest, WalAndSnapshot) {
 TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -2392,8 +2506,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) {
   // Recover snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+        .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
@@ -2402,13 +2518,15 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) {
   // Recover snapshot and create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .recover_on_startup = true,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .recover_on_startup = true,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateExtendedDataset(db.storage());
@@ -2420,15 +2538,17 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot and WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2440,8 +2560,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) {
 TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) {
   // Create snapshot.
   {
-    memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                     .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}};
+    memgraph::storage::Config config{
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -2455,8 +2577,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) {
   // Recover snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+        .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam());
@@ -2465,13 +2589,15 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) {
   // Recover snapshot and create WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .recover_on_startup = true,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .recover_on_startup = true,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateExtendedDataset(db.storage());
@@ -2486,17 +2612,19 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) {
   memgraph::storage::Gid vertex_gid;
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .recover_on_startup = true,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .recover_on_startup = true,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     vertex_gid = vertex.Gid();
     if (GetParam()) {
@@ -2512,14 +2640,16 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot and WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam(),
                 /* verify_info = */ false);
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto labels = vertex->Labels(memgraph::storage::View::OLD);
@@ -2537,7 +2667,7 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) {
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2550,16 +2680,18 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) {
   // Create unrelated WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::minutes(20),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = kFlushWalEvery}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::minutes(20),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = kFlushWalEvery},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -2578,13 +2710,15 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) {
   // Create snapshot and WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::seconds(2),
-            .wal_file_size_kibibytes = 1,
-            .wal_file_flush_every_n_tx = 1}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::seconds(2),
+                       .wal_file_size_kibibytes = 1,
+                       .wal_file_flush_every_n_tx = 1},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     // Restore unrelated snapshots after the database has been started.
@@ -2592,7 +2726,7 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) {
     memgraph::utils::Timer timer;
     // Allow at least 6 snapshots to be created.
     while (timer.Elapsed().count() < 13.0) {
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       acc->CreateVertex();
       ASSERT_FALSE(acc->Commit().HasError());
       ++items_created;
@@ -2613,11 +2747,13 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) {
     // Recover and verify data.
     {
       memgraph::storage::Config config{
-          .items = {.properties_on_edges = GetParam()},
-          .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+          .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+          .salient = {.items = {.properties_on_edges = GetParam()}},
+      };
       memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
       memgraph::dbms::Database db{config, repl_state};
-      auto acc = db.storage()->Access();
+      auto acc = db.Access();
       for (uint64_t j = 0; j < items_created; ++j) {
         auto vertex = acc->FindVertex(memgraph::storage::Gid::FromUint(j), memgraph::storage::View::OLD);
         ASSERT_TRUE(vertex);
@@ -2633,8 +2769,10 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) {
   ASSERT_DEATH(
       ([&]() {
         memgraph::storage::Config config{
-            .items = {.properties_on_edges = GetParam()},
-            .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+
+            .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+            .salient = {.items = {.properties_on_edges = GetParam()}},
+        };
         memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
         memgraph::dbms::Database db{config, repl_state};
       }())  // iile
@@ -2647,14 +2785,16 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) {
   // Create unrelated snapshot and WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::seconds(2)}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::seconds(2)},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
       acc->CreateVertex();
     }
@@ -2670,11 +2810,13 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) {
   // Create snapshot and WALs.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {
-            .storage_directory = storage_directory,
-            .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-            .snapshot_interval = std::chrono::seconds(2)}};
+
+        .durability = {.storage_directory = storage_directory,
+                       .snapshot_wal_mode =
+                           memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+                       .snapshot_interval = std::chrono::seconds(2)},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -2697,15 +2839,17 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot and WALs.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory, .recover_on_startup = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory, .recover_on_startup = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
 
   // Try to use the storage.
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2718,8 +2862,10 @@ TEST_P(DurabilityTest, ParallelConstraintsRecovery) {
   // Create snapshot.
   {
     memgraph::storage::Config config{
-        .items = {.properties_on_edges = GetParam()},
-        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true, .items_per_batch = 13}};
+
+        .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true, .items_per_batch = 13},
+        .salient = {.items = {.properties_on_edges = GetParam()}},
+    };
     memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
     memgraph::dbms::Database db{config, repl_state};
     CreateBaseDataset(db.storage(), GetParam());
@@ -2734,17 +2880,19 @@ TEST_P(DurabilityTest, ParallelConstraintsRecovery) {
   ASSERT_EQ(GetBackupWalsList().size(), 0);
 
   // Recover snapshot.
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory,
-                                                  .recover_on_startup = true,
-                                                  .snapshot_on_exit = false,
-                                                  .items_per_batch = 13,
-                                                  .allow_parallel_index_creation = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory,
+                     .recover_on_startup = true,
+                     .snapshot_on_exit = false,
+                     .items_per_batch = 13,
+                     .allow_parallel_index_creation = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
   memgraph::dbms::Database db{config, repl_state};
   VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam());
   {
-    auto acc = db.storage()->Access();
+    auto acc = db.Access();
     auto vertex = acc->CreateVertex();
     auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et"));
     ASSERT_TRUE(edge.HasValue());
@@ -2754,12 +2902,14 @@ TEST_P(DurabilityTest, ParallelConstraintsRecovery) {
 
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(DurabilityTest, ConstraintsRecoveryFunctionSetting) {
-  memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()},
-                                   .durability = {.storage_directory = storage_directory,
-                                                  .recover_on_startup = true,
-                                                  .snapshot_on_exit = false,
-                                                  .items_per_batch = 13,
-                                                  .allow_parallel_schema_creation = true}};
+  memgraph::storage::Config config{
+      .durability = {.storage_directory = storage_directory,
+                     .recover_on_startup = true,
+                     .snapshot_on_exit = false,
+                     .items_per_batch = 13,
+                     .allow_parallel_schema_creation = true},
+      .salient = {.items = {.properties_on_edges = GetParam()}},
+  };
   // Create snapshot.
   {
     config.durability.recover_on_startup = false;
diff --git a/tests/unit/storage_v2_edge_inmemory.cpp b/tests/unit/storage_v2_edge_inmemory.cpp
index 6f3b0da02..50ae1f14f 100644
--- a/tests/unit/storage_v2_edge_inmemory.cpp
+++ b/tests/unit/storage_v2_edge_inmemory.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using testing::UnorderedElementsAre;
 
 class StorageEdgeTest : public ::testing::TestWithParam<bool> {};
@@ -27,13 +28,13 @@ INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Valu
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -43,7 +44,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -117,7 +118,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -218,13 +219,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->CreateVertex();
     auto vertex_from = acc->CreateVertex();
     gid_to = vertex_to.Gid();
@@ -234,7 +235,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -302,7 +303,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -391,12 +392,12 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -404,7 +405,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -462,7 +463,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -537,13 +538,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -553,7 +554,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Create edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -621,7 +622,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -650,7 +651,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -718,7 +719,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -807,13 +808,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->CreateVertex();
     auto vertex_from = acc->CreateVertex();
     gid_to = vertex_to.Gid();
@@ -823,7 +824,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Create edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -891,7 +892,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -920,7 +921,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -988,7 +989,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1077,12 +1078,12 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -1090,7 +1091,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Create edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1148,7 +1149,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1167,7 +1168,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1225,7 +1226,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1304,13 +1305,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -1320,7 +1321,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1388,7 +1389,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1475,7 +1476,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Delete edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1542,7 +1543,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1573,13 +1574,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->CreateVertex();
     auto vertex_from = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -1589,7 +1590,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1657,7 +1658,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1744,7 +1745,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Delete edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1811,7 +1812,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1842,12 +1843,12 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -1855,7 +1856,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1913,7 +1914,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1990,7 +1991,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Delete edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -2047,7 +2048,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -2068,13 +2069,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -2084,7 +2085,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2152,7 +2153,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2239,7 +2240,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Delete the edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2306,7 +2307,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2393,7 +2394,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Delete the edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2460,7 +2461,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2491,13 +2492,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -2507,7 +2508,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2575,7 +2576,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2662,7 +2663,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Delete the edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2729,7 +2730,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2817,7 +2818,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Delete the edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2884,7 +2885,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2915,12 +2916,12 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -2928,7 +2929,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -2986,7 +2987,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3063,7 +3064,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Delete the edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3120,7 +3121,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3197,7 +3198,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Delete the edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3254,7 +3255,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3275,13 +3276,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
 
@@ -3330,7 +3331,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3394,7 +3395,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex_from);
@@ -3415,13 +3416,13 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
 
@@ -3548,7 +3549,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -3686,7 +3687,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex1);
@@ -3745,13 +3746,13 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
 
@@ -3800,7 +3801,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Detach delete vertex, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3864,7 +3865,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3905,7 +3906,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3969,7 +3970,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex_from);
@@ -3990,13 +3991,13 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}}));
   memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
 
@@ -4123,7 +4124,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Detach delete vertex, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -4261,7 +4262,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -4439,7 +4440,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -4577,7 +4578,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex1);
@@ -4636,10 +4637,10 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithProperties, EdgePropertyCommit) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}}));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -4682,7 +4683,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4711,7 +4712,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
     acc->Abort();
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4743,7 +4744,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4767,12 +4768,12 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithProperties, EdgePropertyAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}}));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create the vertex.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -4785,7 +4786,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to "nandare", but abort the transaction.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4826,7 +4827,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is null.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4848,7 +4849,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to "nandare".
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4889,7 +4890,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is "nandare".
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4920,7 +4921,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to null, but abort the transaction.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4962,7 +4963,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is "nandare".
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4993,7 +4994,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to null.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5035,7 +5036,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is null.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5059,10 +5060,10 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithProperties, EdgePropertySerializationError) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}}));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5073,8 +5074,8 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
 
-  auto acc1 = store->Access();
-  auto acc2 = store->Access();
+  auto acc1 = store->Access(ReplicationRole::MAIN);
+  auto acc2 = store->Access(ReplicationRole::MAIN);
 
   // Set property 1 to 123 in accessor 1.
   {
@@ -5139,7 +5140,7 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
 
   // Check which properties exist.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5169,12 +5170,12 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
 
 TEST(StorageWithProperties, EdgePropertyClear) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}}));
   memgraph::storage::Gid gid;
   auto property1 = store->NameToProperty("property1");
   auto property2 = store->NameToProperty("property2");
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5190,7 +5191,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5223,7 +5224,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     acc->Abort();
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5235,7 +5236,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5269,7 +5270,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5285,10 +5286,10 @@ TEST(StorageWithProperties, EdgePropertyClear) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithoutProperties, EdgePropertyAbort) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = false}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = false}}}));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5299,7 +5300,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5330,7 +5331,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) {
     acc->Abort();
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5354,10 +5355,10 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) {
 
 TEST(StorageWithoutProperties, EdgePropertyClear) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = false}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = false}}}));
   memgraph::storage::Gid gid;
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5368,7 +5369,7 @@ TEST(StorageWithoutProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -5381,11 +5382,11 @@ TEST(StorageWithoutProperties, EdgePropertyClear) {
 
 TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) {
   std::unique_ptr<memgraph::storage::Storage> store(
-      new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}}));
+      new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}}));
 
   auto property = store->NameToProperty("property");
 
-  auto acc = store->Access();
+  auto acc = store->Access(ReplicationRole::MAIN);
   auto vertex = acc->CreateVertex();
   auto edge = acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge"));
   ASSERT_TRUE(edge.HasValue());
diff --git a/tests/unit/storage_v2_edge_ondisk.cpp b/tests/unit/storage_v2_edge_ondisk.cpp
index 5d57c784f..7f3357b10 100644
--- a/tests/unit/storage_v2_edge_ondisk.cpp
+++ b/tests/unit/storage_v2_edge_ondisk.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -18,6 +18,7 @@
 #include "storage/v2/disk/storage.hpp"
 #include "storage/v2/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using testing::UnorderedElementsAre;
 
 class StorageEdgeTest : public ::testing::TestWithParam<bool> {};
@@ -30,14 +31,14 @@ const std::string testSuite = "storage_v2_edge_ondisk";
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -47,7 +48,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -121,7 +122,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -223,14 +224,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->CreateVertex();
     auto vertex_from = acc->CreateVertex();
     gid_to = vertex_to.Gid();
@@ -240,7 +241,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -308,7 +309,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -398,13 +399,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -412,7 +413,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -470,7 +471,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -546,14 +547,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -563,7 +564,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Create edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -631,7 +632,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -660,7 +661,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -728,7 +729,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -818,14 +819,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->CreateVertex();
     auto vertex_from = acc->CreateVertex();
     gid_to = vertex_to.Gid();
@@ -835,7 +836,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Create edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -903,7 +904,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -932,7 +933,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1000,7 +1001,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1090,13 +1091,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -1104,7 +1105,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Create edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1162,7 +1163,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1181,7 +1182,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1239,7 +1240,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1319,14 +1320,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -1336,7 +1337,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1404,7 +1405,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1491,7 +1492,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Delete edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1558,7 +1559,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1590,14 +1591,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->CreateVertex();
     auto vertex_from = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -1607,7 +1608,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1675,7 +1676,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1762,7 +1763,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Delete edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1829,7 +1830,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -1861,13 +1862,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -1875,7 +1876,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -1933,7 +1934,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -2010,7 +2011,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Delete edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -2067,7 +2068,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -2089,14 +2090,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -2106,7 +2107,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2174,7 +2175,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2261,7 +2262,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Delete the edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2328,7 +2329,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2415,7 +2416,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Delete the edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2482,7 +2483,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2514,14 +2515,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertices
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
     gid_from = vertex_from.Gid();
@@ -2531,7 +2532,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2599,7 +2600,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2686,7 +2687,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Delete the edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2753,7 +2754,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2841,7 +2842,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Delete the edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2908,7 +2909,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -2940,13 +2941,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid_vertex = vertex.Gid();
     ASSERT_FALSE(acc->Commit().HasError());
@@ -2954,7 +2955,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Create edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3012,7 +3013,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3089,7 +3090,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Delete the edge, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3146,7 +3147,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3223,7 +3224,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Delete the edge
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3280,7 +3281,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 
   // Check whether the edge exists
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex);
 
@@ -3302,14 +3303,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
 
@@ -3358,7 +3359,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3422,7 +3423,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex_from);
@@ -3444,14 +3445,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
 
@@ -3578,7 +3579,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -3716,7 +3717,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex1);
@@ -3776,14 +3777,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->CreateVertex();
     auto vertex_to = acc->CreateVertex();
 
@@ -3832,7 +3833,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Detach delete vertex, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3896,7 +3897,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -3937,7 +3938,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex_from);
@@ -4001,7 +4002,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW);
     auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex_from);
@@ -4023,14 +4024,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = GetParam();
+  config.salient.items.properties_on_edges = GetParam();
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->CreateVertex();
     auto vertex2 = acc->CreateVertex();
 
@@ -4157,7 +4158,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Detach delete vertex, but abort the transaction
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -4295,7 +4296,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -4473,7 +4474,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Detach delete vertex
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_TRUE(vertex1);
@@ -4611,7 +4612,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 
   // Check dataset
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW);
     auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW);
     ASSERT_FALSE(vertex1);
@@ -4671,11 +4672,11 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithProperties, EdgePropertyCommit) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = true;
+  config.salient.items.properties_on_edges = true;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -4718,7 +4719,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -4748,7 +4749,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
     acc->Abort();
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -4781,7 +4782,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -4807,13 +4808,13 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithProperties, EdgePropertyAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = true;
+  config.salient.items.properties_on_edges = true;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
 
   // Create the vertex.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -4826,7 +4827,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to "nandare", but abort the transaction.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -4868,7 +4869,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is null.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
     auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0];
@@ -4890,7 +4891,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to "nandare".
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -4932,7 +4933,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is "nandare".
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -4964,7 +4965,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to null, but abort the transaction.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5007,7 +5008,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is "nandare".
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5039,7 +5040,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Set property 5 to null.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5082,7 +5083,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 
   // Check that property 5 is null.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5108,11 +5109,11 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithProperties, EdgePropertySerializationError) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = true;
+  config.salient.items.properties_on_edges = true;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5123,8 +5124,8 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
 
-  auto acc1 = store->Access();
-  auto acc2 = store->Access();
+  auto acc1 = store->Access(ReplicationRole::MAIN);
+  auto acc2 = store->Access(ReplicationRole::MAIN);
 
   // Set property 1 to 123 in accessor 1.
   {
@@ -5195,7 +5196,7 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
 
   // Check which properties exist.
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5227,13 +5228,13 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
 
 TEST(StorageWithProperties, EdgePropertyClear) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = true;
+  config.salient.items.properties_on_edges = true;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid;
   auto property1 = store->NameToProperty("property1");
   auto property2 = store->NameToProperty("property2");
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5249,7 +5250,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5283,7 +5284,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     acc->Abort();
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5296,7 +5297,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5331,7 +5332,7 @@ TEST(StorageWithProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5349,11 +5350,11 @@ TEST(StorageWithProperties, EdgePropertyClear) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST(StorageWithoutProperties, EdgePropertyAbort) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = false;
+  config.salient.items.properties_on_edges = false;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     spdlog::trace("Created vertex with gid: {}", gid.AsInt());
@@ -5365,7 +5366,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5397,7 +5398,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) {
     acc->Abort();
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5423,11 +5424,11 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) {
 
 TEST(StorageWithoutProperties, EdgePropertyClear) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = false;
+  config.salient.items.properties_on_edges = false;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
   memgraph::storage::Gid gid;
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->CreateVertex();
     gid = vertex.Gid();
     auto et = acc->NameToEdgeType("et5");
@@ -5438,7 +5439,7 @@ TEST(StorageWithoutProperties, EdgePropertyClear) {
     ASSERT_FALSE(acc->Commit().HasError());
   }
   {
-    auto acc = store->Access();
+    auto acc = store->Access(ReplicationRole::MAIN);
     auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD);
     ASSERT_TRUE(vertex);
 
@@ -5453,12 +5454,12 @@ TEST(StorageWithoutProperties, EdgePropertyClear) {
 
 TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) {
   auto config = disk_test_utils::GenerateOnDiskConfig(testSuite);
-  config.items.properties_on_edges = true;
+  config.salient.items.properties_on_edges = true;
   std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config));
 
   auto property = store->NameToProperty("property");
 
-  auto acc = store->Access();
+  auto acc = store->Access(ReplicationRole::MAIN);
   auto vertex = acc->CreateVertex();
   auto edge = acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge"));
   ASSERT_TRUE(edge.HasValue());
diff --git a/tests/unit/storage_v2_gc.cpp b/tests/unit/storage_v2_gc.cpp
index 122558c17..e619f3723 100644
--- a/tests/unit/storage_v2_gc.cpp
+++ b/tests/unit/storage_v2_gc.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -14,6 +14,7 @@
 
 #include "storage/v2/inmemory/storage.hpp"
 
+using memgraph::replication_coordination_glue::ReplicationRole;
 using testing::UnorderedElementsAre;
 
 // TODO: The point of these is not to test GC fully, these are just simple
@@ -31,7 +32,7 @@ TEST(StorageV2Gc, Sanity) {
   std::vector<memgraph::storage::Gid> vertices;
 
   {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     // Create some vertices, but delete some of them immediately.
     for (uint64_t i = 0; i < 1000; ++i) {
       auto vertex = acc->CreateVertex();
@@ -63,7 +64,7 @@ TEST(StorageV2Gc, Sanity) {
 
   // Verify existing vertices and add labels to some of them.
   {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     for (uint64_t i = 0; i < 1000; ++i) {
       auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD);
       EXPECT_EQ(vertex.has_value(), i % 5 != 0);
@@ -101,7 +102,7 @@ TEST(StorageV2Gc, Sanity) {
 
   // Add and remove some edges.
   {
-    auto acc = storage->Access();
+    auto acc = storage->Access(ReplicationRole::MAIN);
     for (uint64_t i = 0; i < 1000; ++i) {
       auto from_vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD);
       auto to_vertex = acc->FindVertex(vertices[(i + 1) % 1000], memgraph::storage::View::OLD);
@@ -171,13 +172,13 @@ TEST(StorageV2Gc, Indices) {
       std::make_unique<memgraph::storage::InMemoryStorage>(memgraph::storage::Config{
           .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::milliseconds(100)}}));
   {
-    auto unique_acc = storage->UniqueAccess();
+    auto unique_acc = storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(storage->NameToLabel("label")).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
   {
-    auto acc0 = storage->Access();
+    auto acc0 = storage->Access(ReplicationRole::MAIN);
     for (uint64_t i = 0; i < 1000; ++i) {
       auto vertex = acc0->CreateVertex();
       ASSERT_TRUE(*vertex.AddLabel(acc0->NameToLabel("label")));
@@ -185,9 +186,9 @@ TEST(StorageV2Gc, Indices) {
     ASSERT_FALSE(acc0->Commit().HasError());
   }
   {
-    auto acc1 = storage->Access();
+    auto acc1 = storage->Access(ReplicationRole::MAIN);
 
-    auto acc2 = storage->Access();
+    auto acc2 = storage->Access(ReplicationRole::MAIN);
     for (auto vertex : acc2->Vertices(memgraph::storage::View::OLD)) {
       ASSERT_TRUE(*vertex.RemoveLabel(acc2->NameToLabel("label")));
     }
diff --git a/tests/unit/storage_v2_get_info.cpp b/tests/unit/storage_v2_get_info.cpp
index bb7d5c0a1..c0f7e2dbc 100644
--- a/tests/unit/storage_v2_get_info.cpp
+++ b/tests/unit/storage_v2_get_info.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,7 +22,7 @@
 
 // NOLINTNEXTLINE(google-build-using-namespace)
 using namespace memgraph::storage;
-
+using memgraph::replication_coordination_glue::ReplicationRole;
 constexpr auto testSuite = "storage_v2_get_info";
 const std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / testSuite};
 
@@ -63,22 +63,22 @@ TYPED_TEST(InfoTest, InfoCheck) {
 
   {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->CreateExistenceConstraint(lbl, prop).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       ASSERT_FALSE(unique_acc->DropExistenceConstraint(lbl, prop).HasError());
       ASSERT_FALSE(unique_acc->Commit().HasError());
     }
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto v1 = acc->CreateVertex();
     auto v2 = acc->CreateVertex();
     auto v3 = acc->CreateVertex();
     auto v4 = acc->CreateVertex();
-    auto v5 = acc->CreateVertex();
+    [[maybe_unused]] auto v5 = acc->CreateVertex();
 
     ASSERT_FALSE(v2.AddLabel(lbl).HasError());
     ASSERT_FALSE(v3.AddLabel(lbl).HasError());
@@ -93,49 +93,49 @@ TYPED_TEST(InfoTest, InfoCheck) {
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(lbl).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop2).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->DropIndex(lbl, prop).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl, {prop2}).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl2, {prop}).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl3, {prop}).HasError());
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     ASSERT_EQ(unique_acc->DropUniqueConstraint(lbl, {prop2}),
               memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS);
     ASSERT_FALSE(unique_acc->Commit().HasError());
   }
 
-  StorageInfo info = this->storage->GetInfo(true);  // force to use configured directory
+  StorageInfo info = this->storage->GetInfo(true, ReplicationRole::MAIN);  // force to use configured directory
 
   ASSERT_EQ(info.vertex_count, 5);
   ASSERT_EQ(info.edge_count, 2);
diff --git a/tests/unit/storage_v2_indices.cpp b/tests/unit/storage_v2_indices.cpp
index 142e15522..8ee053087 100644
--- a/tests/unit/storage_v2_indices.cpp
+++ b/tests/unit/storage_v2_indices.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -25,7 +25,7 @@
 
 // NOLINTNEXTLINE(google-build-using-namespace)
 using namespace memgraph::storage;
-
+using memgraph::replication_coordination_glue::ReplicationRole;
 using testing::IsEmpty;
 using testing::Types;
 using testing::UnorderedElementsAre;
@@ -39,7 +39,7 @@ class IndexTest : public testing::Test {
   void SetUp() override {
     config_ = disk_test_utils::GenerateOnDiskConfig(testSuite);
     this->storage = std::make_unique<StorageType>(config_);
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     this->prop_id = acc->NameToProperty("id");
     this->prop_val = acc->NameToProperty("val");
     this->label1 = acc->NameToLabel("label1");
@@ -89,13 +89,13 @@ TYPED_TEST_CASE(IndexTest, StorageTypes);
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(IndexTest, LabelIndexCreate) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelIndexExists(this->label1));
     EXPECT_EQ(acc->ListAllIndices().label.size(), 0);
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 10; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2));
@@ -104,19 +104,19 @@ TYPED_TEST(IndexTest, LabelIndexCreate) {
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9));
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9));
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 10; i < 20; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2));
@@ -138,7 +138,7 @@ TYPED_TEST(IndexTest, LabelIndexCreate) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 10; i < 20; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2));
@@ -159,7 +159,7 @@ TYPED_TEST(IndexTest, LabelIndexCreate) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD),
                 UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW),
@@ -179,13 +179,13 @@ TYPED_TEST(IndexTest, LabelIndexCreate) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(IndexTest, LabelIndexDrop) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelIndexExists(this->label1));
     EXPECT_EQ(acc->ListAllIndices().label.size(), 0);
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 10; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2));
@@ -194,41 +194,41 @@ TYPED_TEST(IndexTest, LabelIndexDrop) {
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9));
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9));
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->DropIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelIndexExists(this->label1));
     EXPECT_EQ(acc->ListAllIndices().label.size(), 0);
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_TRUE(unique_acc->DropIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelIndexExists(this->label1));
     EXPECT_EQ(acc->ListAllIndices().label.size(), 0);
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 10; i < 20; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2));
@@ -237,18 +237,18 @@ TYPED_TEST(IndexTest, LabelIndexDrop) {
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_TRUE(acc->LabelIndexExists(this->label1));
     EXPECT_THAT(acc->ListAllIndices().label, UnorderedElementsAre(this->label1));
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
 
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD),
                 UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
@@ -274,17 +274,17 @@ TYPED_TEST(IndexTest, LabelIndexBasic) {
   //    vertices.
   // 4. Delete even numbered vertices.
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
-  auto acc = this->storage->Access();
+  auto acc = this->storage->Access(ReplicationRole::MAIN);
   EXPECT_THAT(acc->ListAllIndices().label, UnorderedElementsAre(this->label1, this->label2));
   EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), IsEmpty());
   EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), IsEmpty());
@@ -347,18 +347,18 @@ TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) {
   // the same vertex in the index (they only differ by the timestamp). This test
   // checks that duplicates are properly filtered out.
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 5; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
@@ -370,7 +370,7 @@ TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4));
 
     for (auto vertex : acc->Vertices(View::OLD)) {
@@ -395,19 +395,19 @@ TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) {
 TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) {
   // Check that transactions only see entries they are supposed to see.
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
-  auto acc_before = this->storage->Access();
-  auto acc = this->storage->Access();
-  auto acc_after = this->storage->Access();
+  auto acc_before = this->storage->Access(ReplicationRole::MAIN);
+  auto acc = this->storage->Access(ReplicationRole::MAIN);
+  auto acc_after = this->storage->Access(ReplicationRole::MAIN);
 
   for (int i = 0; i < 5; ++i) {
     auto vertex = this->CreateVertex(acc.get());
@@ -422,7 +422,7 @@ TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) {
 
   ASSERT_NO_ERROR(acc->Commit());
 
-  auto acc_after_commit = this->storage->Access();
+  auto acc_after_commit = this->storage->Access(ReplicationRole::MAIN);
 
   EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, View::NEW), View::NEW), IsEmpty());
 
@@ -436,17 +436,17 @@ TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) {
 TYPED_TEST(IndexTest, LabelIndexCountEstimate) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 20; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(i % 3 ? this->label1 : this->label2));
@@ -460,23 +460,23 @@ TYPED_TEST(IndexTest, LabelIndexCountEstimate) {
 TYPED_TEST(IndexTest, LabelIndexDeletedVertex) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
     auto vertex2 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex2.AddLabel(this->label1));
     EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
     ASSERT_NO_ERROR(acc1->Commit());
-    auto acc2 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW);
     auto res = acc2->DeleteVertex(&*vertex_to_delete);
     ASSERT_FALSE(res.HasError());
     ASSERT_NO_ERROR(acc2->Commit());
-    auto acc3 = this->storage->Access();
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1));
   }
 }
@@ -484,23 +484,23 @@ TYPED_TEST(IndexTest, LabelIndexDeletedVertex) {
 TYPED_TEST(IndexTest, LabelIndexRemoveIndexedLabel) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
     auto vertex2 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex2.AddLabel(this->label1));
     ASSERT_NO_ERROR(acc1->Commit());
-    auto acc2 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc2->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
     auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW);
     auto res = vertex_to_delete->RemoveLabel(this->label1);
     ASSERT_FALSE(res.HasError());
     ASSERT_NO_ERROR(acc2->Commit());
-    auto acc3 = this->storage->Access();
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1));
   }
 }
@@ -508,17 +508,17 @@ TYPED_TEST(IndexTest, LabelIndexRemoveIndexedLabel) {
 TYPED_TEST(IndexTest, LabelIndexRemoveAndAddIndexedLabel) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex1 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
     auto vertex2 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex2.AddLabel(this->label1));
     ASSERT_NO_ERROR(acc1->Commit());
-    auto acc2 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc2->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
     auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW);
     auto res_remove = vertex_to_delete->RemoveLabel(this->label1);
@@ -526,7 +526,7 @@ TYPED_TEST(IndexTest, LabelIndexRemoveAndAddIndexedLabel) {
     auto res_add = vertex_to_delete->AddLabel(this->label1);
     ASSERT_FALSE(res_add.HasError());
     ASSERT_NO_ERROR(acc2->Commit());
-    auto acc3 = this->storage->Access();
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
   }
 }
@@ -537,11 +537,11 @@ TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) {
         static_cast<memgraph::storage::DiskLabelIndex *>(this->storage->indices_.label_index_.get());
 
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(10)));
@@ -550,14 +550,14 @@ TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) {
     auto *tx_db = disk_label_index->GetRocksDBStorage()->db_;
     ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-    auto acc2 = this->storage->Access(std::nullopt);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value();
     ASSERT_TRUE(vertex2.SetProperty(this->prop_val, memgraph::storage::PropertyValue(10)).HasValue());
     ASSERT_FALSE(acc2->Commit().HasError());
 
     ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-    auto acc3 = this->storage->Access(std::nullopt);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value();
     ASSERT_TRUE(vertex3.SetProperty(this->prop_val, memgraph::storage::PropertyValue(15)).HasValue());
     ASSERT_FALSE(acc3->Commit().HasError());
@@ -569,92 +569,92 @@ TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(IndexTest, LabelPropertyIndexCreateAndDrop) {
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_EQ(acc->ListAllIndices().label_property.size(), 0);
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_id).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_TRUE(acc->LabelPropertyIndexExists(this->label1, this->prop_id));
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllIndices().label_property,
                 UnorderedElementsAre(std::make_pair(this->label1, this->prop_id)));
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label2, this->prop_id));
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_TRUE(unique_acc->CreateIndex(this->label1, this->prop_id).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllIndices().label_property,
                 UnorderedElementsAre(std::make_pair(this->label1, this->prop_id)));
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label2, this->prop_id).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_TRUE(acc->LabelPropertyIndexExists(this->label2, this->prop_id));
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(
         acc->ListAllIndices().label_property,
         UnorderedElementsAre(std::make_pair(this->label1, this->prop_id), std::make_pair(this->label2, this->prop_id)));
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->DropIndex(this->label1, this->prop_id).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label1, this->prop_id));
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(acc->ListAllIndices().label_property,
                 UnorderedElementsAre(std::make_pair(this->label2, this->prop_id)));
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_TRUE(unique_acc->DropIndex(this->label1, this->prop_id).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->DropIndex(this->label2, this->prop_id).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label2, this->prop_id));
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_EQ(acc->ListAllIndices().label_property.size(), 0);
   }
 }
@@ -667,17 +667,17 @@ TYPED_TEST(IndexTest, LabelPropertyIndexCreateAndDrop) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(IndexTest, LabelPropertyIndexBasic) {
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label2, this->prop_val).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
-  auto acc = this->storage->Access();
+  auto acc = this->storage->Access(ReplicationRole::MAIN);
   EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), IsEmpty());
 
   for (int i = 0; i < 10; ++i) {
@@ -762,13 +762,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexBasic) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) {
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 5; ++i) {
       auto vertex = this->CreateVertex(acc.get());
       ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
@@ -782,7 +782,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) {
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD),
                 UnorderedElementsAre(0, 1, 2, 3, 4));
 
@@ -809,14 +809,14 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TYPED_TEST(IndexTest, LabelPropertyIndexTransactionalIsolation) {
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
-  auto acc_before = this->storage->Access();
-  auto acc = this->storage->Access();
-  auto acc_after = this->storage->Access();
+  auto acc_before = this->storage->Access(ReplicationRole::MAIN);
+  auto acc = this->storage->Access(ReplicationRole::MAIN);
+  auto acc_after = this->storage->Access(ReplicationRole::MAIN);
 
   for (int i = 0; i < 5; ++i) {
     auto vertex = this->CreateVertex(acc.get());
@@ -833,7 +833,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexTransactionalIsolation) {
 
   ASSERT_NO_ERROR(acc->Commit());
 
-  auto acc_after_commit = this->storage->Access();
+  auto acc_after_commit = this->storage->Access(ReplicationRole::MAIN);
 
   EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty());
 
@@ -852,13 +852,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) {
   // properly.
 
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
 
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
 
     for (int i = 0; i < 10; ++i) {
       auto vertex = this->CreateVertex(acc.get());
@@ -868,7 +868,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) {
     ASSERT_NO_ERROR(acc->Commit());
   }
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 0; i < 5; ++i) {
       EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, PropertyValue(i), View::OLD)),
                   UnorderedElementsAre(2 * i, 2 * i + 1));
@@ -926,12 +926,12 @@ TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) {
 TYPED_TEST(IndexTest, LabelPropertyIndexCountEstimate) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
 
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (int i = 1; i <= 10; ++i) {
       for (int j = 0; j < i; ++j) {
         auto vertex = this->CreateVertex(acc.get());
@@ -954,7 +954,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexCountEstimate) {
 
 TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) {
   {
-    auto unique_acc = this->storage->UniqueAccess();
+    auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
     EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
     ASSERT_NO_ERROR(unique_acc->Commit());
   }
@@ -993,7 +993,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) {
 
   // Create vertices, each with one of the values above.
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     for (const auto &value : values) {
       auto v = acc->CreateVertex();
       ASSERT_TRUE(v.AddLabel(this->label1).HasValue());
@@ -1004,7 +1004,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) {
 
   // Verify that all nodes are in the index.
   {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto iterable = acc->Vertices(this->label1, this->prop_val, View::OLD);
     auto it = iterable.begin();
     for (const auto &value : values) {
@@ -1021,7 +1021,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) {
   auto verify = [&](const std::optional<memgraph::utils::Bound<PropertyValue>> &from,
                     const std::optional<memgraph::utils::Bound<PropertyValue>> &to,
                     const std::vector<PropertyValue> &expected) {
-    auto acc = this->storage->Access();
+    auto acc = this->storage->Access(ReplicationRole::MAIN);
     auto iterable = acc->Vertices(this->label1, this->prop_val, from, to, View::OLD);
     size_t i = 0;
     for (auto it = iterable.begin(); it != iterable.end(); ++it, ++i) {
@@ -1166,11 +1166,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) {
 TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
 
     auto vertex1 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
@@ -1183,13 +1183,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) {
     EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW);
     auto res = acc2->DeleteVertex(&*vertex_to_delete);
     ASSERT_FALSE(res.HasError());
     ASSERT_NO_ERROR(acc2->Commit());
 
-    auto acc3 = this->storage->Access();
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, this->prop_val, View::NEW), View::NEW),
                 UnorderedElementsAre(1));
   }
@@ -1199,11 +1199,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) {
 TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
 
     auto vertex1 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
@@ -1216,13 +1216,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) {
     EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW);
     auto res = vertex_to_delete->RemoveLabel(this->label1);
     ASSERT_FALSE(res.HasError());
     ASSERT_NO_ERROR(acc2->Commit());
 
-    auto acc3 = this->storage->Access();
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, this->prop_val, View::NEW), View::NEW),
                 UnorderedElementsAre(1));
   }
@@ -1231,11 +1231,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) {
 TYPED_TEST(IndexTest, LabelPropertyIndexRemoveAndAddIndexedLabel) {
   if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) {
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
 
     auto vertex1 = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex1.AddLabel(this->label1));
@@ -1248,7 +1248,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexRemoveAndAddIndexedLabel) {
     EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1));
     ASSERT_NO_ERROR(acc1->Commit());
 
-    auto acc2 = this->storage->Access();
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto target_vertex = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW);
     auto remove_res = target_vertex->RemoveLabel(this->label1);
     ASSERT_FALSE(remove_res.HasError());
@@ -1264,11 +1264,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexClearOldDataFromDisk) {
         static_cast<memgraph::storage::DiskLabelPropertyIndex *>(this->storage->indices_.label_property_index_.get());
 
     {
-      auto unique_acc = this->storage->UniqueAccess();
+      auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN);
       EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError());
       ASSERT_NO_ERROR(unique_acc->Commit());
     }
-    auto acc1 = this->storage->Access();
+    auto acc1 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex = this->CreateVertex(acc1.get());
     ASSERT_NO_ERROR(vertex.AddLabel(this->label1));
     ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(10)));
@@ -1277,14 +1277,14 @@ TYPED_TEST(IndexTest, LabelPropertyIndexClearOldDataFromDisk) {
     auto *tx_db = disk_label_property_index->GetRocksDBStorage()->db_;
     ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-    auto acc2 = this->storage->Access(std::nullopt);
+    auto acc2 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value();
     ASSERT_TRUE(vertex2.SetProperty(this->prop_val, memgraph::storage::PropertyValue(10)).HasValue());
     ASSERT_FALSE(acc2->Commit().HasError());
 
     ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1);
 
-    auto acc3 = this->storage->Access(std::nullopt);
+    auto acc3 = this->storage->Access(ReplicationRole::MAIN);
     auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value();
     ASSERT_TRUE(vertex3.SetProperty(this->prop_val, memgraph::storage::PropertyValue(15)).HasValue());
     ASSERT_FALSE(acc3->Commit().HasError());
diff --git a/tests/unit/storage_v2_isolation_level.cpp b/tests/unit/storage_v2_isolation_level.cpp
index d2ae14d8f..39d7a92ec 100644
--- a/tests/unit/storage_v2_isolation_level.cpp
+++ b/tests/unit/storage_v2_isolation_level.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,6 +16,7 @@
 #include "storage/v2/inmemory/storage.hpp"
 #include "storage/v2/isolation_level.hpp"
 #include "utils/on_scope_exit.hpp"
+using memgraph::replication_coordination_glue::ReplicationRole;
 
 namespace {
 int64_t VerticesCount(memgraph::storage::Storage::Accessor *accessor) {
@@ -44,9 +45,9 @@ class StorageIsolationLevelTest : public ::testing::TestWithParam<memgraph::stor
   void TestVisibility(std::unique_ptr<memgraph::storage::Storage> &storage,
                       const memgraph::storage::IsolationLevel &default_isolation_level,
                       const memgraph::storage::IsolationLevel &override_isolation_level) {
-    auto creator = storage->Access();
-    auto default_isolation_level_reader = storage->Access();
-    auto override_isolation_level_reader = storage->Access(override_isolation_level);
+    auto creator = storage->Access(ReplicationRole::MAIN);
+    auto default_isolation_level_reader = storage->Access(ReplicationRole::MAIN);
+    auto override_isolation_level_reader = storage->Access(ReplicationRole::MAIN, override_isolation_level);
 
     ASSERT_EQ(VerticesCount(default_isolation_level_reader.get()), 0);
     ASSERT_EQ(VerticesCount(override_isolation_level_reader.get()), 0);
@@ -89,7 +90,7 @@ class StorageIsolationLevelTest : public ::testing::TestWithParam<memgraph::stor
     ASSERT_FALSE(override_isolation_level_reader->Commit().HasError());
 
     SCOPED_TRACE("Visibility after a new transaction is started");
-    auto verifier = storage->Access();
+    auto verifier = storage->Access(ReplicationRole::MAIN);
     ASSERT_EQ(VerticesCount(verifier.get()), iteration_count);
     ASSERT_FALSE(verifier->Commit().HasError());
   }
diff --git a/tests/unit/storage_v2_replication.cpp b/tests/unit/storage_v2_replication.cpp
index f07130c4a..e572440ca 100644
--- a/tests/unit/storage_v2_replication.cpp
+++ b/tests/unit/storage_v2_replication.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,6 +22,7 @@
 #include <storage/v2/inmemory/storage.hpp>
 #include <storage/v2/property_value.hpp>
 #include <storage/v2/replication/enums.hpp>
+#include "auth/auth.hpp"
 #include "dbms/database.hpp"
 #include "dbms/dbms_handler.hpp"
 #include "dbms/replication_handler.hpp"
@@ -31,6 +32,7 @@
 #include "storage/v2/indices/label_index_stats.hpp"
 #include "storage/v2/storage.hpp"
 #include "storage/v2/view.hpp"
+#include "utils/rw_lock.hpp"
 #include "utils/synchronized.hpp"
 
 using testing::UnorderedElementsAre;
@@ -39,9 +41,9 @@ using memgraph::dbms::RegisterReplicaError;
 using memgraph::dbms::ReplicationHandler;
 using memgraph::dbms::UnregisterReplicaResult;
 using memgraph::replication::ReplicationClientConfig;
-using memgraph::replication::ReplicationMode;
-using memgraph::replication::ReplicationRole;
 using memgraph::replication::ReplicationServerConfig;
+using memgraph::replication_coordination_glue::ReplicationMode;
+using memgraph::replication_coordination_glue::ReplicationRole;
 using memgraph::storage::Config;
 using memgraph::storage::EdgeAccessor;
 using memgraph::storage::Gid;
@@ -64,26 +66,35 @@ class ReplicationTest : public ::testing::Test {
   void TearDown() override { Clear(); }
 
   Config main_conf = [&] {
-    Config config{.items = {.properties_on_edges = true},
-                  .durability = {
-                      .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-                  }};
+    Config config{
+        .durability =
+            {
+                .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+            },
+        .salient.items = {.properties_on_edges = true},
+    };
     UpdatePaths(config, storage_directory);
     return config;
   }();
   Config repl_conf = [&] {
-    Config config{.items = {.properties_on_edges = true},
-                  .durability = {
-                      .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-                  }};
+    Config config{
+        .durability =
+            {
+                .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+            },
+        .salient.items = {.properties_on_edges = true},
+    };
     UpdatePaths(config, repl_storage_directory);
     return config;
   }();
   Config repl2_conf = [&] {
-    Config config{.items = {.properties_on_edges = true},
-                  .durability = {
-                      .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
-                  }};
+    Config config{
+        .durability =
+            {
+                .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
+            },
+        .salient.items = {.properties_on_edges = true},
+    };
     UpdatePaths(config, repl2_storage_directory);
     return config;
   }();
@@ -102,20 +113,23 @@ class ReplicationTest : public ::testing::Test {
 
 struct MinMemgraph {
   MinMemgraph(const memgraph::storage::Config &conf)
-      : dbms{conf
+      : auth{conf.durability.storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}},
+        dbms{conf
 #ifdef MG_ENTERPRISE
              ,
-             reinterpret_cast<
-                 memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *>(0),
-             true, false
+             &auth, true
 #endif
         },
         repl_state{dbms.ReplicationState()},
-        db{*dbms.Get().get()},
+        db_acc{dbms.Get()},
+        db{*db_acc.get()},
         repl_handler(dbms) {
   }
+
+  memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth;
   memgraph::dbms::DbmsHandler dbms;
   memgraph::replication::ReplicationState &repl_state;
+  memgraph::dbms::DatabaseAccess db_acc;
   memgraph::dbms::Database &db;
   ReplicationHandler repl_handler;
 };
@@ -152,7 +166,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
     ASSERT_TRUE(v.AddLabel(main.db.storage()->NameToLabel(vertex_label)).HasValue());
     ASSERT_TRUE(v.SetProperty(main.db.storage()->NameToProperty(vertex_property), PropertyValue(vertex_property_value))
                     .HasValue());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   {
@@ -178,7 +192,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
     auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
     ASSERT_TRUE(v->RemoveLabel(main.db.storage()->NameToLabel(vertex_label)).HasValue());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   {
@@ -197,7 +211,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
     auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
     ASSERT_TRUE(acc->DeleteVertex(&*v).HasValue());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   {
@@ -224,7 +238,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
     ASSERT_TRUE(edge.SetProperty(main.db.storage()->NameToProperty(edge_property), PropertyValue(edge_property_value))
                     .HasValue());
     edge_gid.emplace(edge.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   const auto find_edge = [&](const auto &edges, const Gid edge_gid) -> std::optional<EdgeAccessor> {
@@ -261,7 +275,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
     auto edge = find_edge(out_edges->edges, *edge_gid);
     ASSERT_TRUE(edge);
     ASSERT_TRUE(acc->DeleteEdge(&*edge).HasValue());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   {
@@ -287,25 +301,25 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
   {
     auto unique_acc = main.db.UniqueAccess();
     ASSERT_FALSE(unique_acc->CreateIndex(main.db.storage()->NameToLabel(label)).HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
     unique_acc->SetIndexStats(main.db.storage()->NameToLabel(label), l_stats);
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
     ASSERT_FALSE(
         unique_acc->CreateIndex(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property))
             .HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
     unique_acc->SetIndexStats(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property),
                               lp_stats);
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
@@ -313,7 +327,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
                      ->CreateExistenceConstraint(main.db.storage()->NameToLabel(label),
                                                  main.db.storage()->NameToProperty(property))
                      .HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
@@ -322,7 +336,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
                                               {main.db.storage()->NameToProperty(property),
                                                main.db.storage()->NameToProperty(property_extra)})
                      .HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
 
   {
@@ -360,24 +374,24 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
   {
     auto unique_acc = main.db.UniqueAccess();
     unique_acc->DeleteLabelIndexStats(main.db.storage()->NameToLabel(label));
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
     ASSERT_FALSE(unique_acc->DropIndex(main.db.storage()->NameToLabel(label)).HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
     unique_acc->DeleteLabelPropertyIndexStats(main.db.storage()->NameToLabel(label));
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
     ASSERT_FALSE(
         unique_acc->DropIndex(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property))
             .HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
@@ -385,7 +399,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
                      ->DropExistenceConstraint(main.db.storage()->NameToLabel(label),
                                                main.db.storage()->NameToProperty(property))
                      .HasError());
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
   {
     auto unique_acc = main.db.UniqueAccess();
@@ -393,7 +407,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) {
                   main.db.storage()->NameToLabel(label),
                   {main.db.storage()->NameToProperty(property), main.db.storage()->NameToProperty(property_extra)}),
               memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS);
-    ASSERT_FALSE(unique_acc->Commit().HasError());
+    ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError());
   }
 
   {
@@ -455,21 +469,21 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) {
     ASSERT_TRUE(v.SetProperty(main.db.storage()->NameToProperty(vertex_property), PropertyValue(vertex_property_value))
                     .HasValue());
     vertex_gid.emplace(v.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
-  const auto check_replica = [&](Storage *replica_store) {
-    auto acc = replica_store->Access();
+  const auto check_replica = [&](memgraph::dbms::Database &replica_database) {
+    auto acc = replica_database.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
     const auto labels = v->Labels(View::OLD);
     ASSERT_TRUE(labels.HasValue());
-    ASSERT_THAT(*labels, UnorderedElementsAre(replica_store->NameToLabel(vertex_label)));
+    ASSERT_THAT(*labels, UnorderedElementsAre(replica_database.storage()->NameToLabel(vertex_label)));
     ASSERT_FALSE(acc->Commit().HasError());
   };
 
-  check_replica(replica1.db.storage());
-  check_replica(replica2.db.storage());
+  check_replica(replica1.db);
+  check_replica(replica2.db);
 
   auto handler = main.repl_handler;
   handler.UnregisterReplica(replicas[1]);
@@ -477,12 +491,12 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) {
     auto acc = main.db.Access();
     auto v = acc->CreateVertex();
     vertex_gid.emplace(v.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   // REPLICA1 should contain the new vertex
   {
-    auto acc = replica1.db.storage()->Access();
+    auto acc = replica1.db.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
     ASSERT_FALSE(acc->Commit().HasError());
@@ -490,7 +504,7 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) {
 
   // REPLICA2 should not contain the new vertex
   {
-    auto acc = replica2.db.storage()->Access();
+    auto acc = replica2.db.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_FALSE(v);
     ASSERT_FALSE(acc->Commit().HasError());
@@ -515,7 +529,7 @@ TEST_F(ReplicationTest, RecoveryProcess) {
       // Create the vertex before registering a replica
       auto v = acc->CreateVertex();
       vertex_gids.emplace_back(v.Gid());
-      ASSERT_FALSE(acc->Commit().HasError());
+      ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
     }
   }
 
@@ -531,13 +545,13 @@ TEST_F(ReplicationTest, RecoveryProcess) {
       auto acc = main.db.Access();
       auto v = acc->CreateVertex();
       vertex_gids.emplace_back(v.Gid());
-      ASSERT_FALSE(acc->Commit().HasError());
+      ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
     }
     {
       auto acc = main.db.Access();
       auto v = acc->CreateVertex();
       vertex_gids.emplace_back(v.Gid());
-      ASSERT_FALSE(acc->Commit().HasError());
+      ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
     }
   }
 
@@ -560,7 +574,7 @@ TEST_F(ReplicationTest, RecoveryProcess) {
       ASSERT_TRUE(
           v->SetProperty(main.db.storage()->NameToProperty(property_name), PropertyValue(property_value)).HasValue());
     }
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
 
   static constexpr const auto *vertex_label = "vertex_label";
@@ -594,7 +608,7 @@ TEST_F(ReplicationTest, RecoveryProcess) {
         ASSERT_TRUE(v);
         ASSERT_TRUE(v->AddLabel(main.db.storage()->NameToLabel(vertex_label)).HasValue());
       }
-      ASSERT_FALSE(acc->Commit().HasError());
+      ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
     }
     {
       auto acc = replica.db.Access();
@@ -663,7 +677,7 @@ TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) {
     auto acc = main.db.Access();
     auto v = acc->CreateVertex();
     created_vertices.push_back(v.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
 
     if (i == 0) {
       ASSERT_EQ(main.db.storage()->GetReplicaState("REPLICA_ASYNC"), ReplicaState::REPLICATING);
@@ -677,7 +691,7 @@ TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) {
   }
 
   ASSERT_TRUE(std::all_of(created_vertices.begin(), created_vertices.end(), [&](const auto vertex_gid) {
-    auto acc = replica_async.db.storage()->Access();
+    auto acc = replica_async.db.Access();
     auto v = acc->FindVertex(vertex_gid, View::OLD);
     const bool exists = v.has_value();
     EXPECT_FALSE(acc->Commit().HasError());
@@ -723,16 +737,16 @@ TEST_F(ReplicationTest, EpochTest) {
     auto acc = main.db.Access();
     const auto v = acc->CreateVertex();
     vertex_gid.emplace(v.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
   {
-    auto acc = replica1.db.storage()->Access();
+    auto acc = replica1.db.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
   {
-    auto acc = replica2.db.storage()->Access();
+    auto acc = replica2.db.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
     ASSERT_FALSE(acc->Commit().HasError());
@@ -756,17 +770,17 @@ TEST_F(ReplicationTest, EpochTest) {
   {
     auto acc = main.db.Access();
     acc->CreateVertex();
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
   {
-    auto acc = replica1.db.storage()->Access();
+    auto acc = replica1.db.Access();
     auto v = acc->CreateVertex();
     vertex_gid.emplace(v.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, replica1.db_acc).HasError());
   }
   // Replica1 should forward it's vertex to Replica2
   {
-    auto acc = replica2.db.storage()->Access();
+    auto acc = replica2.db.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_TRUE(v);
     ASSERT_FALSE(acc->Commit().HasError());
@@ -790,12 +804,12 @@ TEST_F(ReplicationTest, EpochTest) {
     auto acc = main.db.Access();
     const auto v = acc->CreateVertex();
     vertex_gid.emplace(v.Gid());
-    ASSERT_FALSE(acc->Commit().HasError());
+    ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError());
   }
   // Replica1 is not compatible with the main so it shouldn't contain
   // it's newest vertex
   {
-    auto acc = replica1.db.storage()->Access();
+    auto acc = replica1.db.Access();
     const auto v = acc->FindVertex(*vertex_gid, View::OLD);
     ASSERT_FALSE(v);
     ASSERT_FALSE(acc->Commit().HasError());
@@ -926,7 +940,7 @@ TEST_F(ReplicationTest, ReplicationReplicaWithExistingEndPoint) {
                       .ip_address = local_host,
                       .port = common_port,
                   })
-                  .GetError() == RegisterReplicaError::END_POINT_EXISTS);
+                  .GetError() == RegisterReplicaError::ENDPOINT_EXISTS);
 }
 
 TEST_F(ReplicationTest, RestoringReplicationAtStartupAfterDroppingReplica) {
diff --git a/tests/unit/storage_v2_show_storage_info.cpp b/tests/unit/storage_v2_show_storage_info.cpp
index da788978e..73d33a77d 100644
--- a/tests/unit/storage_v2_show_storage_info.cpp
+++ b/tests/unit/storage_v2_show_storage_info.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -44,7 +44,7 @@ class ShowStorageInfoTest : public testing::Test {
 };
 
 TEST_F(ShowStorageInfoTest, CountOnAbort) {
-  auto acc = this->storage->Access();
+  auto acc = this->storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
   auto src_vertex = acc->CreateVertex();
   auto dest_vertex = acc->CreateVertex();
   auto et = acc->NameToEdgeType("et5");
diff --git a/tests/unit/storage_v2_storage_mode.cpp b/tests/unit/storage_v2_storage_mode.cpp
index 49ee633c5..487319d3c 100644
--- a/tests/unit/storage_v2_storage_mode.cpp
+++ b/tests/unit/storage_v2_storage_mode.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -44,8 +44,8 @@ TEST_P(StorageModeTest, Mode) {
           .transaction{.isolation_level = memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION}});
 
   static_cast<memgraph::storage::InMemoryStorage *>(storage.get())->SetStorageMode(storage_mode);
-  auto creator = storage->Access();
-  auto other_analytics_mode_reader = storage->Access();
+  auto creator = storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
+  auto other_analytics_mode_reader = storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN);
 
   ASSERT_EQ(CountVertices(*creator, memgraph::storage::View::OLD), 0);
   ASSERT_EQ(CountVertices(*other_analytics_mode_reader, memgraph::storage::View::OLD), 0);
diff --git a/tests/unit/typed_value.cpp b/tests/unit/typed_value.cpp
index 5f2f8f3bf..41dd6e3ba 100644
--- a/tests/unit/typed_value.cpp
+++ b/tests/unit/typed_value.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -37,7 +37,8 @@ class AllTypesFixture : public testing::Test {
   std::vector<TypedValue> values_;
   memgraph::storage::Config config_{disk_test_utils::GenerateOnDiskConfig(testSuite)};
   std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config_)};
-  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()};
+  std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{
+      db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)};
   memgraph::query::DbAccessor dba{storage_dba.get()};
 
   void SetUp() override {
diff --git a/tests/unit/utils_string.cpp b/tests/unit/utils_string.cpp
index cefe57a6a..fdf64ae9f 100644
--- a/tests/unit/utils_string.cpp
+++ b/tests/unit/utils_string.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -171,3 +171,22 @@ TEST(String, Substr) {
   EXPECT_EQ(Substr(string, string.size() - 1, 1), string.substr(string.size() - 1, 1));
   EXPECT_EQ(Substr(string, string.size() - 1, 2), string.substr(string.size() - 1, 2));
 }
+
+TEST(String, DoubleToString) {
+  EXPECT_EQ(DoubleToString(0), "0");
+  EXPECT_EQ(DoubleToString(1), "1");
+  EXPECT_EQ(DoubleToString(1234567890123456), "1234567890123456");
+  EXPECT_EQ(DoubleToString(static_cast<double>(12345678901234567)), "12345678901234568");
+  EXPECT_EQ(DoubleToString(0.5), "0.5");
+  EXPECT_EQ(DoubleToString(1.0), "1");
+  EXPECT_EQ(DoubleToString(5.8), "5.8");
+  EXPECT_EQ(DoubleToString(1.01234000), "1.01234");
+  EXPECT_EQ(DoubleToString(1.036837585345), "1.036837585345");
+  EXPECT_EQ(DoubleToString(103.6837585345), "103.683758534500001");
+  EXPECT_EQ(DoubleToString(1.01234567890123456789), "1.012345678901235");
+  EXPECT_EQ(DoubleToString(1234567.01234567891234567), "1234567.012345678871498");
+  EXPECT_EQ(DoubleToString(0.00001), "0.00001");
+  EXPECT_EQ(DoubleToString(0.00000000000001), "0.00000000000001");
+  EXPECT_EQ(DoubleToString(0.000000000000001), "0.000000000000001");
+  EXPECT_EQ(DoubleToString(0.0000000000000001), "0");
+}