diff --git a/.github/img/grafana.png b/.github/img/grafana.png
new file mode 100644
index 0000000..c8b4f8c
Binary files /dev/null and b/.github/img/grafana.png differ
diff --git a/README.md b/README.md
index 0f9e075..ddfbb91 100644
--- a/README.md
+++ b/README.md
@@ -59,4 +59,13 @@ class Example {
         }
     }
 }
-```
\ No newline at end of file
+```
+
+
+Stress testing
+----------
+
+If you want to stress test the server, you can run the script from the `noiser` module.
+Before starting stress testing, you'd better run Prometheus and Grafana from `docker-compose.yml`.
+In Grafana, you can see this view:
+![Grafana](.github/img/grafana.png)
\ No newline at end of file
diff --git a/RoomPicker-Grafana-Dashboard.json b/RoomPicker-Grafana-Dashboard.json
index 8225074..e2abb7c 100644
--- a/RoomPicker-Grafana-Dashboard.json
+++ b/RoomPicker-Grafana-Dashboard.json
@@ -453,7 +453,7 @@
             "axisPlacement": "left",
             "barAlignment": 0,
             "drawStyle": "bars",
-            "fillOpacity": 30,
+            "fillOpacity": 26,
             "gradientMode": "none",
             "hideFrom": {
               "legend": false,
@@ -462,7 +462,7 @@
             },
             "insertNulls": false,
             "lineInterpolation": "stepAfter",
-            "lineWidth": 2,
+            "lineWidth": 3,
             "pointSize": 3,
             "scaleDistribution": {
               "type": "linear"
@@ -471,7 +471,7 @@
             "spanNulls": false,
             "stacking": {
               "group": "A",
-              "mode": "normal"
+              "mode": "none"
             },
             "thresholdsStyle": {
               "mode": "off"
@@ -725,13 +725,13 @@
     "list": []
   },
   "time": {
-    "from": "now-6h",
+    "from": "now-15m",
     "to": "now"
   },
   "timepicker": {},
   "timezone": "",
   "title": "RoomPicker",
   "uid": "e3009deb-a342-4a32-9d13-3b44ed3461bc",
-  "version": 24,
+  "version": 26,
   "weekStart": ""
 }
\ No newline at end of file
diff --git a/client-impl/src/main/java/ru/dragonestia/picker/api/impl/repository/NodeRepositoryImpl.java b/client-impl/src/main/java/ru/dragonestia/picker/api/impl/repository/NodeRepositoryImpl.java
index 08034a3..2c195b3 100644
--- a/client-impl/src/main/java/ru/dragonestia/picker/api/impl/repository/NodeRepositoryImpl.java
+++ b/client-impl/src/main/java/ru/dragonestia/picker/api/impl/repository/NodeRepositoryImpl.java
@@ -76,6 +76,10 @@ public void saveNode(@NotNull NodeDefinition definition) {
 
     @Override
     public @NotNull PickedRoomResponse pickRoom(@NotNull NodeIdentifier identifier, @NotNull Set<UserIdentifier> users) {
-        throw new UnsupportedOperationException("Not implemented");
+        return rest.queryPostWithBody(
+                "/nodes/" + identifier.getValue() + "/pick",
+                PickedRoomResponse.class,
+                params -> {}, String.join(",", users.stream().map(user -> user.getValue()).toList())
+        );
     }
 }
diff --git a/client-impl/src/main/java/ru/dragonestia/picker/api/impl/util/RestTemplate.java b/client-impl/src/main/java/ru/dragonestia/picker/api/impl/util/RestTemplate.java
index 4d1dbbe..9e440be 100644
--- a/client-impl/src/main/java/ru/dragonestia/picker/api/impl/util/RestTemplate.java
+++ b/client-impl/src/main/java/ru/dragonestia/picker/api/impl/util/RestTemplate.java
@@ -3,9 +3,7 @@
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import okhttp3.FormBody;
-import okhttp3.OkHttpClient;
-import okhttp3.Response;
+import okhttp3.*;
 import org.jetbrains.annotations.ApiStatus.Internal;
 import ru.dragonestia.picker.api.exception.ExceptionFactory;
 import ru.dragonestia.picker.api.impl.RoomPickerClient;
@@ -78,6 +76,22 @@ public <T> T query(String uri, HttpMethod method, Class<T> clazz, ParamsConsumer
         }
     }
 
+    public <T> T queryPostWithBody(String uri, Class<T> clazz, ParamsConsumer paramsConsumer, String body) {
+        var request = client.prepareRequestBuilder(uri + queryEncode(paramsConsumer))
+                .post(RequestBody.create(body, MediaType.get("text/plain")))
+                .build();
+
+        try (var response = httpClient.newCall(request).execute()) {
+            checkResponseForErrors(response);
+
+            return json.readValue(new String(Objects.requireNonNull(response.body()).bytes(), StandardCharsets.UTF_8), clazz);
+        } catch (JsonProcessingException ex) {
+            throw new RuntimeException("Json processing error", ex);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     private String queryEncode(ParamsConsumer paramsConsumer) {
         var params = new HashMap<String, String>();
         paramsConsumer.accept(params);
diff --git a/control-panel/frontend/generated/index.ts b/control-panel/frontend/generated/index.ts
new file mode 100644
index 0000000..1b1823b
--- /dev/null
+++ b/control-panel/frontend/generated/index.ts
@@ -0,0 +1,32 @@
+/******************************************************************************
+ * This file is auto-generated by Vaadin.
+ * If you want to customize the entry point, you can copy this file or create
+ * your own `index.ts` in your frontend directory.
+ * By default, the `index.ts` file should be in `./frontend/` folder.
+ *
+ * NOTE:
+ *     - You need to restart the dev-server after adding the new `index.ts` file.
+ *       After that, all modifications to `index.ts` are recompiled automatically.
+ *     - `index.js` is also supported if you don't want to use TypeScript.
+ ******************************************************************************/
+
+// import Vaadin client-router to handle client-side and server-side navigation
+import { Router } from '@vaadin/router';
+
+// import Flow module to enable navigation to Vaadin server-side views
+import { Flow } from 'Frontend/generated/jar-resources/Flow.js';
+
+const { serverSideRoutes } = new Flow({
+  imports: () => import('Frontend/generated/flow/generated-flow-imports.js')
+});
+
+const routes = [
+  // for client-side, place routes below (more info https://hilla.dev/docs/lit/guides/routing#initializing-the-router)
+
+  // for server-side, the next magic line sends all unmatched routes:
+  ...serverSideRoutes // IMPORTANT: this must be the last entry in the array
+];
+
+// Vaadin router needs an outlet in the index.html page to display views
+const router = new Router(document.querySelector('#outlet'));
+router.setRoutes(routes);
diff --git a/noiser/build.gradle b/noiser/build.gradle
new file mode 100644
index 0000000..0efd63f
--- /dev/null
+++ b/noiser/build.gradle
@@ -0,0 +1,22 @@
+plugins {
+    id 'java'
+}
+
+group = 'ru.dragonestia'
+version = 'unspecified'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation project(":client-api")
+    implementation project(":client-impl")
+
+    testImplementation platform('org.junit:junit-bom:5.9.1')
+    testImplementation 'org.junit.jupiter:junit-jupiter'
+}
+
+test {
+    useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/noiser/src/main/java/ru/dragonestia/picker/noiser/Main.java b/noiser/src/main/java/ru/dragonestia/picker/noiser/Main.java
new file mode 100644
index 0000000..75eba8d
--- /dev/null
+++ b/noiser/src/main/java/ru/dragonestia/picker/noiser/Main.java
@@ -0,0 +1,127 @@
+package ru.dragonestia.picker.noiser;
+
+import ru.dragonestia.picker.api.impl.RoomPickerClient;
+import ru.dragonestia.picker.api.model.node.NodeDefinition;
+import ru.dragonestia.picker.api.model.node.PickingMethod;
+import ru.dragonestia.picker.api.model.room.RoomDefinition;
+import ru.dragonestia.picker.api.repository.query.node.GetAllNodes;
+import ru.dragonestia.picker.api.repository.query.user.UnlinkUsersFromRoom;
+import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
+import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
+import ru.dragonestia.picker.api.repository.type.UserIdentifier;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+public class Main {
+
+    private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(8);
+    private final Random random = new Random();
+    private final RoomPickerClient client;
+    private final List<NodeIdentifier> nodes;
+
+    private final Map<NodeIdentifier, AtomicInteger> totalUsers = new ConcurrentHashMap<>();
+    private final int expectedUsers = 10000;
+
+    public Main() {
+        client = new RoomPickerClient("http://localhost:8080", "admin", "qwerty123");
+        nodes = initNodes();
+    }
+
+    private void removeAll() {
+        client.getNodeRepository().allNodes(GetAllNodes.JUST)
+                .forEach(node -> client.getNodeRepository().removeNode(node));
+    }
+
+    private List<NodeIdentifier> initNodes() {
+        removeAll();
+
+        var list = new ArrayList<NodeIdentifier>();
+
+        for (int i = 0; i < 5; i++) {
+            var node = new NodeDefinition(NodeIdentifier.of("test-node-" + i))
+                    .setPickingMethod(PickingMethod.values()[i % PickingMethod.values().length]);
+
+            client.getNodeRepository().saveNode(node);
+
+            var nodeId = node.getIdentifierObject();
+            totalUsers.put(nodeId, new AtomicInteger(0));
+            list.add(nodeId);
+        }
+
+        return list;
+    }
+
+    private void initRooms() {
+        final int perNode = expectedUsers / nodes.size();
+        final int roomsPerNode = perNode / 10;
+
+        for (var nodeId: nodes) {
+            for (int i = 0; i < roomsPerNode; i++) {
+                client.getRoomRepository().saveRoom(
+                        new RoomDefinition(nodeId, RoomIdentifier.of(UUID.randomUUID().toString())).setMaxSlots(10)
+                );
+            }
+        }
+    }
+
+    private void pickUsers() {
+        for (var nodeId: nodes) {
+            var usersInNode = totalUsers.get(nodeId);
+
+            try {
+                synchronized (usersInNode) {
+                    var users = new HashSet<UserIdentifier>();
+                    var maxAdd = Math.min(10, (expectedUsers / nodes.size()) - usersInNode.get());
+
+                    if (maxAdd == 0) return;
+                    var add = maxAdd == 1 ? 1 : (random.nextInt(maxAdd - 1) + 1);
+                    for (int i = 0; i < add; i++) {
+                        users.add(UserIdentifier.of(UUID.randomUUID().toString()));
+                    }
+
+                    var request = client.getNodeRepository().pickRoom(nodeId, users);
+                    usersInNode.addAndGet(add);
+                    var roomId = RoomIdentifier.of(request.roomId());
+
+                    System.out.printf("Added %s(total %s) users to %s/%s%n", add, usersInNode.get(), nodeId.getValue(), roomId.getValue());
+
+                    scheduler.schedule(() -> {
+                        try {
+                            client.getUserRepository().unlinkUsersFromRoom(UnlinkUsersFromRoom.builder()
+                                    .setNodeId(nodeId)
+                                    .setRoomId(roomId)
+                                    .setUsers(users)
+                                    .build());
+
+                            usersInNode.addAndGet(-add);
+                            System.out.printf("Reduced %s users from %s/%s%n", add, nodeId.getValue(), roomId.getValue());
+                        } catch (Exception ex) {
+                            System.out.println("Error(" + ex.getClass().getSimpleName() + "): " + ex.getMessage());
+                        }
+                    }, random.nextInt(10) + 1, TimeUnit.SECONDS);
+                }
+            } catch (Exception ex) {
+                System.out.println("Error(" + ex.getClass().getSimpleName() + "): " + ex.getMessage());
+            }
+        }
+    }
+
+    public void startNoise() throws InterruptedException {
+        initRooms();
+
+        while (true) {
+            pickUsers();
+            Thread.sleep(10);
+        }
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        new Main().startNoise();
+    }
+}
\ No newline at end of file
diff --git a/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java b/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java
index a268434..db9fd7c 100644
--- a/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java
+++ b/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java
@@ -9,18 +9,16 @@
 import org.aspectj.lang.annotation.After;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
-import org.springframework.context.event.EventListener;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import ru.dragonestia.picker.event.UpdateRoomLockStateEvent;
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.repository.RoomRepository;
 import ru.dragonestia.picker.repository.UserRepository;
+import ru.dragonestia.picker.repository.impl.ContainerRepository;
 
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -30,6 +28,7 @@
 @Log4j2
 public class UserMetricsAspect {
 
+    private final ContainerRepository containerRepository;
     private final RoomRepository roomRepository;
     private final UserRepository userRepository;
     private final MeterRegistry meterRegistry;
@@ -52,33 +51,8 @@ void onUnlinkUsers(Room room) {
         countAllUsers(room);
     }
 
-    @AfterReturning(value = "execution(void ru.dragonestia.picker.repository.RoomRepository.create(ru.dragonestia.picker.model.Room)) && args(room)", argNames = "room")
-    void onCreateRoom(Room room) {
-        checkRoom(room);
-    }
-
-    @After(value = "execution(void ru.dragonestia.picker.repository.UserRepository.onRemoveRoom(ru.dragonestia.picker.model.Room)) && args(room, ..)", argNames = "room")
-    void onRemoveRoom(Room room) {
-        countAllUsers(room);
-    }
-
     private void countAllUsers(Room room) {
         totalUsers.set(userRepository.countAllUsers());
-
-        checkRoom(room);
-    }
-
-    private void checkRoom(Room room) {
-        var set = data.get(room.getNodeIdentifier()).locked();
-        if (room.isLocked()) {
-            set.add(room);
-            return;
-        }
-        if (!room.hasUnlimitedSlots() && userRepository.usersOf(room).size() >= room.getMaxSlots()) {
-            set.add(room);
-            return;
-        }
-        set.remove(room);
     }
 
     @After(value = "execution(void ru.dragonestia.picker.repository.NodeRepository.create(ru.dragonestia.picker.model.Node)) && args(node)", argNames = "node")
@@ -93,7 +67,7 @@ void onCreateNode(Node node) {
                 .baseUnit("1s")
                 .register(meterRegistry);
 
-        var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId).locked().size())
+        var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId).locked())
                 .tag("nodeId", nodeId)
                 .register(meterRegistry);
 
@@ -101,7 +75,7 @@ void onCreateNode(Node node) {
                 .tag("nodeId", nodeId)
                 .register(meterRegistry);
 
-        data.put(nodeId, new NodeData(gauge, new AtomicInteger(0), counter, new HashSet<>(), lockedGauge, roomsGauge));
+        data.put(nodeId, new NodeData(gauge, new AtomicInteger(0), counter, new AtomicInteger(0), lockedGauge, roomsGauge));
     }
 
     @After(value = "execution(* ru.dragonestia.picker.repository.NodeRepository.delete(ru.dragonestia.picker.model.Node)) && args(node)", argNames = "node")
@@ -114,20 +88,28 @@ void onDeleteNode(Node node) {
         meterRegistry.remove(data.roomsGauge());
     }
 
-    @AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pickFree(ru.dragonestia.picker.model.Node, *)) && args(node, ..)", argNames = "node")
+    @AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pick(ru.dragonestia.picker.model.Node, *)) && args(node, ..)", argNames = "node")
     void onPickRoom(Node node) {
         data.get(node.getIdentifier()).picksPerMinute().increment();
     }
 
-    @EventListener
-    void onRoomChangeLockState(UpdateRoomLockStateEvent event) {
-        checkRoom(event.room());
-    }
-
     @Scheduled(fixedDelay = 3_000)
     void updateUserMetrics() {
-        userRepository.countUsersForNodes().forEach((nodeId, users) -> data.get(nodeId).users().set(users));
+        userRepository.countUsersForNodes().forEach((nodeId, users) -> {
+            Optional.ofNullable(data.get(nodeId)).ifPresent(node -> node.users().set(users));
+        });
+
+        containerRepository.all().forEach(nodeContainer -> {
+            var locked = data.get(nodeContainer.getNode().getIdentifier()).locked();
+            locked.set(0);
+
+            nodeContainer.allRooms().forEach(roomContainer -> {
+                if (roomContainer.canBePicked(1)) return;
+
+                locked.incrementAndGet();
+            });
+        });
     }
 
-    private record NodeData(Gauge usersGauge, AtomicInteger users, Counter picksPerMinute, Set<Room> locked, Gauge lockedGauge, Gauge roomsGauge) {}
+    private record NodeData(Gauge usersGauge, AtomicInteger users, Counter picksPerMinute, AtomicInteger locked, Gauge lockedGauge, Gauge roomsGauge) {}
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java b/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java
index c72a527..d07e5d5 100644
--- a/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java
+++ b/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java
@@ -8,17 +8,21 @@
 import org.springframework.web.bind.annotation.*;
 import ru.dragonestia.picker.api.exception.NodeNotFoundException;
 import ru.dragonestia.picker.api.model.node.PickingMethod;
+import ru.dragonestia.picker.api.model.user.UserDefinition;
 import ru.dragonestia.picker.api.repository.response.NodeDetailsResponse;
 import ru.dragonestia.picker.api.repository.response.NodeListResponse;
 import ru.dragonestia.picker.api.repository.response.PickedRoomResponse;
 import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
+import ru.dragonestia.picker.api.repository.type.UserIdentifier;
 import ru.dragonestia.picker.model.Node;
+import ru.dragonestia.picker.model.User;
 import ru.dragonestia.picker.service.NodeService;
 import ru.dragonestia.picker.service.RoomService;
 import ru.dragonestia.picker.util.DetailsParser;
 import ru.dragonestia.picker.util.NamingValidator;
 
 import java.util.Arrays;
+import java.util.stream.Collectors;
 
 @Tag(name = "Nodes", description = "Node management")
 @RestController
@@ -77,12 +81,14 @@ ResponseEntity<?> removeNode(
     @PostMapping("/{nodeId}/pick")
     ResponseEntity<PickedRoomResponse> pickRoom(
             @Parameter(description = "Node identifier") @PathVariable("nodeId") String nodeId,
-            @Parameter(description = "Users to add", example = "user1,user3,user3") @RequestParam(name = "userIds") String userIds
+            @RequestBody String userIds
     ) {
         namingValidator.validateNodeId(nodeId);
 
         var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
-        var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
+        var users = Arrays.stream(userIds.split(","))
+                .map(userId -> new User(UserIdentifier.of(userId)))
+                .collect(Collectors.toSet());
         var response = roomService.pickAvailable(node, users);
 
         return ResponseEntity.ok(response);
diff --git a/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java b/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java
index 2c1a578..9eda134 100644
--- a/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java
+++ b/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java
@@ -57,8 +57,8 @@ ResponseEntity<?> register(
     ) {
         var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
         var room = roomFactory.create(RoomIdentifier.of(roomId), node, slots, payload, persist);
-        room.setLocked(locked);
         roomService.create(room);
+        room.setLocked(locked);
 
         return ResponseEntity.ok().build();
     }
diff --git a/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java b/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java
index 6b5e9b0..180e0fe 100644
--- a/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java
+++ b/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java
@@ -10,8 +10,10 @@
 import ru.dragonestia.picker.api.exception.RoomNotFoundException;
 import ru.dragonestia.picker.api.repository.response.LinkUsersWithRoomResponse;
 import ru.dragonestia.picker.api.repository.response.RoomUserListResponse;
+import ru.dragonestia.picker.api.repository.type.UserIdentifier;
 import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.Node;
+import ru.dragonestia.picker.model.User;
 import ru.dragonestia.picker.service.RoomService;
 import ru.dragonestia.picker.service.NodeService;
 import ru.dragonestia.picker.service.UserService;
@@ -19,6 +21,7 @@
 import ru.dragonestia.picker.util.NamingValidator;
 
 import java.util.Arrays;
+import java.util.stream.Collectors;
 
 @Tag(name = "Users", description = "User management")
 @RequiredArgsConstructor
@@ -54,7 +57,9 @@ ResponseEntity<LinkUsersWithRoomResponse> linkUserWithRoom(
             @Parameter(description = "Ignore slot limitation") @RequestParam(name = "force") boolean force
     ) {
         var room = getNodeAndRoom(nodeId, roomId).room();
-        var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
+        var users = Arrays.stream(userIds.split(","))
+                .map(userId -> new User(UserIdentifier.of(userId)))
+                .collect(Collectors.toSet());
         userService.linkUsersWithRoom(room, users, force);
         var usedSlots = userService.getRoomUsers(room).size();
         return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getMaxSlots()));
@@ -69,7 +74,9 @@ ResponseEntity<?> unlinkUsersForBucket(
     ) {
 
         var room = getNodeAndRoom(nodeId, roomId).room();
-        var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
+        var users = Arrays.stream(userIds.split(","))
+                .map(userId -> new User(UserIdentifier.of(userId)))
+                .collect(Collectors.toSet());
         userService.unlinkUsersFromRoom(room, users);
         return ResponseEntity.ok().build();
     }
diff --git a/server/src/main/java/ru/dragonestia/picker/model/Node.java b/server/src/main/java/ru/dragonestia/picker/model/Node.java
index 36e704c..ab49bdd 100644
--- a/server/src/main/java/ru/dragonestia/picker/model/Node.java
+++ b/server/src/main/java/ru/dragonestia/picker/model/Node.java
@@ -58,4 +58,9 @@ public boolean equals(Object object) {
         }
         return false;
     }
+
+    @Override
+    public String toString() {
+        return "{Node id='%s'}".formatted(identifier);
+    }
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/model/Room.java b/server/src/main/java/ru/dragonestia/picker/model/Room.java
index fb3bfa5..5404fc5 100644
--- a/server/src/main/java/ru/dragonestia/picker/model/Room.java
+++ b/server/src/main/java/ru/dragonestia/picker/model/Room.java
@@ -19,15 +19,13 @@ public class Room implements IRoom {
     private final String payload;
     private final boolean persist;
     private boolean locked = false;
-    private final UpdateRoomLockStateEvent.Listener updateLockStateListener;
 
-    public Room(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist, @Nullable UpdateRoomLockStateEvent.Listener listener) {
+    public Room(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist) {
         this.identifier = identifier.getValue();
         this.nodeIdentifier = node.getIdentifier();
         this.slots = slots;
         this.payload = payload;
         this.persist = persist;
-        this.updateLockStateListener = listener;
     }
 
     @Override
@@ -52,10 +50,6 @@ public boolean isLocked() {
 
     public void setLocked(boolean value) {
         locked = value;
-
-        if (updateLockStateListener != null) {
-            updateLockStateListener.accept(new UpdateRoomLockStateEvent(this));
-        }
     }
 
     @Override
diff --git a/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java b/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java
index 938e6c2..7fee26e 100644
--- a/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java
+++ b/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java
@@ -13,10 +13,8 @@
 @RequiredArgsConstructor
 public class RoomFactory {
 
-    private final ApplicationEventPublisher eventPublisher;
-
     @Contract("_, _, _, _, _ -> new")
     public @NotNull Room create(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist) {
-        return new Room(identifier, node, slots, payload, persist, eventPublisher::publishEvent);
+        return new Room(identifier, node, slots, payload, persist);
     }
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java
index 7aa60da..86b4183 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java
@@ -2,7 +2,6 @@
 
 import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
 import ru.dragonestia.picker.model.Node;
-import ru.dragonestia.picker.model.Room;
 
 import java.util.List;
 import java.util.Optional;
@@ -11,9 +10,9 @@ public interface NodeRepository {
 
     void create(Node node) throws NodeAlreadyExistException;
 
-    List<Room> delete(Node node);
+    void delete(Node node);
 
-    Optional<Node> find(String nodeId);
+    Optional<Node> findById(String nodeId);
 
     List<Node> all();
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java
index 6910fb1..0b17c3a 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java
@@ -1,13 +1,14 @@
 package ru.dragonestia.picker.repository;
 
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
 import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.model.User;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 public interface RoomRepository {
 
@@ -17,11 +18,7 @@ public interface RoomRepository {
 
     Optional<Room> find(Node node, String identifier);
 
-    List<Room> all(Node node);
+    Collection<Room> all(Node node);
 
-    Optional<Room> pickFree(Node node, Collection<User> users);
-
-    void onCreateNode(Node node);
-
-    List<Room> onRemoveNode(Node node);
+    Room pick(Node node, Set<User> users) throws NoRoomsAvailableException;
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java
index b8ccb9e..88d75d1 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java
@@ -10,17 +10,15 @@
 
 public interface UserRepository {
 
-    int linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException;
+    void linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException;
 
     void unlinkWithRoom(Room room, Collection<User> users);
 
-    List<Room> findAllLinkedUserRooms(User user);
+    Collection<Room> findAllLinkedUserRooms(User user);
 
-    void onRemoveRoom(Room room);
+    Collection<User> usersOf(Room room);
 
-    List<User> usersOf(Room room);
-
-    List<User> search(String input);
+    Collection<User> search(String input);
 
     int countAllUsers();
 
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java
new file mode 100644
index 0000000..5d9e02c
--- /dev/null
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java
@@ -0,0 +1,47 @@
+package ru.dragonestia.picker.repository.impl;
+
+import lombok.extern.log4j.Log4j2;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.stereotype.Component;
+import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
+import ru.dragonestia.picker.model.Node;
+import ru.dragonestia.picker.repository.impl.container.NodeContainer;
+import ru.dragonestia.picker.repository.impl.type.UserTransaction;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class ContainerRepository {
+
+    private final Map<String, NodeContainer> containers = new ConcurrentHashMap<>();
+
+    private UserTransaction.Listener transactionListener = transaction -> {};
+
+    public void create(Node node) throws NodeAlreadyExistException {
+        if (containers.containsKey(node.getIdentifier())) {
+            throw new NodeAlreadyExistException(node.getIdentifier());
+        }
+
+        var container = new NodeContainer(node, transactionListener);
+        containers.put(node.getIdentifier(), container);
+    }
+
+    public void remove(@NotNull String nodeId) {
+        containers.remove(nodeId);
+    }
+
+    public @NotNull Optional<NodeContainer> findById(@NotNull String nodeId) {
+        return Optional.ofNullable(containers.get(nodeId));
+    }
+
+    public @NotNull Collection<NodeContainer> all() {
+        return containers.values();
+    }
+
+    public void setTransactionListener(@NotNull UserTransaction.Listener transactionListener) {
+        this.transactionListener = transactionListener;
+    }
+}
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java
index defb133..8503c01 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java
@@ -1,80 +1,39 @@
 package ru.dragonestia.picker.repository.impl;
 
 import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Repository;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
 import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
 import ru.dragonestia.picker.model.Node;
-import ru.dragonestia.picker.model.Room;
-import ru.dragonestia.picker.repository.RoomRepository;
 import ru.dragonestia.picker.repository.NodeRepository;
-import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
+import ru.dragonestia.picker.repository.impl.container.NodeContainer;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-@Repository
+@Component
 @RequiredArgsConstructor
 public class NodeRepositoryImpl implements NodeRepository {
 
-    private final RoomRepository roomRepository;
-    private final PickerRepository pickerRepository;
-    private final NodeId2PickerModeCache nodeId2PickerModeCache;
-    private final Map<String, Node> nodeMap = new HashMap<>();
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private final ContainerRepository containerRepository;
 
     @Override
     public void create(Node node) throws NodeAlreadyExistException {
-        lock.writeLock().lock();
-        try {
-            if (nodeMap.containsKey(node.getIdentifier())) {
-                throw new NodeAlreadyExistException(node.getIdentifier());
-            }
-
-            nodeMap.put(node.getIdentifier(), node);
-            var picker = pickerRepository.create(node.getIdentifier(), node.getPickingMethod());
-            nodeId2PickerModeCache.put(node.getIdentifier(), picker);
-
-            roomRepository.onCreateNode(node);
-        } finally {
-            lock.writeLock().unlock();
-        }
+        containerRepository.create(node);
     }
 
     @Override
-    public List<Room> delete(Node node) {
-        lock.writeLock().lock();
-        try {
-            nodeMap.remove(node.getIdentifier());
-            pickerRepository.remove(node.getIdentifier());
-            nodeId2PickerModeCache.remove(node.getIdentifier());
-
-            return roomRepository.onRemoveNode(node);
-        } finally {
-            lock.writeLock().unlock();
-        }
+    public void delete(Node node) {
+        containerRepository.remove(node.getIdentifier());
     }
 
     @Override
-    public Optional<Node> find(String nodeId) {
-        lock.readLock().lock();
-        try {
-            return nodeMap.containsKey(nodeId)? Optional.of(nodeMap.get(nodeId)) : Optional.empty();
-        } finally {
-            lock.readLock().unlock();
-        }
+    public Optional<Node> findById(String nodeId) {
+        return containerRepository.findById(nodeId).map(NodeContainer::getNode);
     }
 
     @Override
     public List<Node> all() {
-        lock.readLock().lock();
-        try {
-            return nodeMap.values().stream().toList();
-        } finally {
-            lock.readLock().unlock();
-        }
+        return containerRepository.all().stream().map(NodeContainer::getNode).toList();
     }
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java
deleted file mode 100644
index 536a0c3..0000000
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package ru.dragonestia.picker.repository.impl;
-
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Component;
-import ru.dragonestia.picker.api.model.node.PickingMethod;
-import ru.dragonestia.picker.model.Room;
-import ru.dragonestia.picker.model.User;
-import ru.dragonestia.picker.repository.UserRepository;
-import ru.dragonestia.picker.repository.impl.picker.*;
-
-import java.security.InvalidParameterException;
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Component
-@RequiredArgsConstructor
-public class PickerRepository {
-
-    private final UserRepository userRepository;
-    private final Map<String, RoomPicker> pickers = new ConcurrentHashMap<>();
-
-    public RoomPicker create(String nodeId, PickingMethod mode) {
-        var picker = of(mode);
-        pickers.put(nodeId, picker);
-        return picker;
-    }
-
-    public void remove(String nodeId) {
-        pickers.remove(nodeId);
-    }
-
-    public RoomPicker find(String nodeId) {
-        return pickers.get(nodeId);
-    }
-
-    public Room pick(String nodeId, Collection<User> users) {
-        return pickers.get(nodeId).pick(users);
-    }
-
-    private RoomPicker of(PickingMethod mode) {
-        switch (mode) {
-            case SEQUENTIAL_FILLING -> {
-                return new SequentialFillingPicker(userRepository);
-            }
-
-            case ROUND_ROBIN -> {
-                return new RoundRobinPicker(userRepository);
-            }
-
-            case LEAST_PICKED -> {
-                return new LeastPickedPicker(userRepository);
-            }
-
-            default -> throw new InvalidParameterException("Taken: " + mode);
-        }
-    }
-}
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java
index 8d76f11..fe570a0 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java
@@ -1,163 +1,61 @@
 package ru.dragonestia.picker.repository.impl;
 
 import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Repository;
+import org.springframework.stereotype.Component;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.api.exception.NodeNotFoundException;
 import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
-import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.Node;
+import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.User;
 import ru.dragonestia.picker.repository.RoomRepository;
-import ru.dragonestia.picker.repository.UserRepository;
+import ru.dragonestia.picker.repository.impl.container.NodeContainer;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
 
-@Repository
+@Component
 @RequiredArgsConstructor
 public class RoomRepositoryImpl implements RoomRepository {
 
-    private final UserRepository userRepository;
-    private final PickerRepository pickerRepository;
-    private final Map<Node, Rooms> node2roomsMap = new HashMap<>();
-    private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
+    private final ContainerRepository containerRepository;
 
     @Override
     public void create(Room room) throws RoomAlreadyExistException {
-        var nodeId = room.getNodeIdentifier();
-
-        lock.writeLock().lock();
-        try {
-            var node = node2roomsMap.keySet().stream()
-                    .filter(n -> room.getNodeIdentifier().equals(n.getIdentifier()))
-                    .findFirst();
-
-            if (node.isEmpty()) {
-                throw new IllegalArgumentException("Node '" + nodeId + "' does not exist");
-            }
-
-            var rooms = node2roomsMap.get(node.get());
-            if (rooms.containsKey(room.getIdentifier())) {
-                throw new RoomAlreadyExistException(room.getNodeIdentifier(), room.getIdentifier());
-            }
-            rooms.put(room.getIdentifier(), new RoomContainer(room, new AtomicInteger(0)));
-            pickerRepository.find(room.getNodeIdentifier()).add(room);
-        } finally {
-            lock.writeLock().unlock();
-        }
+        containerRepository.findById(room.getNodeIdentifier())
+                .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()))
+                .addRoom(room);
     }
 
     @Override
     public void remove(Room room) {
-        var nodeId = room.getNodeIdentifier();
-        var node = node2roomsMap.keySet().stream()
-                .filter(n -> room.getNodeIdentifier().equals(n.getIdentifier()))
-                .findFirst();
-
-        lock.writeLock().lock();
-        try {
-            if (node.isEmpty()) {
-                throw new NodeNotFoundException("Node '" + nodeId + "' does not exist");
-            }
-
-            node2roomsMap.get(node.get()).remove(room.getIdentifier());
-            pickerRepository.find(room.getNodeIdentifier()).remove(room);
-
-            userRepository.onRemoveRoom(room);
-        } finally {
-            lock.writeLock().unlock();
-        }
+        containerRepository.findById(room.getNodeIdentifier())
+                .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()))
+                .removeRoom(room);
     }
 
     @Override
     public Optional<Room> find(Node node, String identifier) {
-        lock.readLock().lock();
-        try {
-            if (!node2roomsMap.containsKey(node)) {
-                throw new NodeNotFoundException("Node '" + node.getIdentifier() + "' does not exist");
-            }
-
-            var result = node2roomsMap.get(node).getOrDefault(identifier, null);
-            return result == null? Optional.empty() : Optional.of(result.room());
-        } finally {
-            lock.readLock().unlock();
-        }
+        return containerRepository.findById(node.getIdentifier())
+                .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
+                .findRoomById(identifier)
+                .map(RoomContainer::getRoom);
     }
 
     @Override
-    public List<Room> all(Node node) {
-        lock.readLock().lock();
-        try {
-            if (!node2roomsMap.containsKey(node)) {
-                throw new NodeNotFoundException("Node '%s' does not exists".formatted(node.getIdentifier()));
-            }
-
-            return node2roomsMap.get(node).values().stream().map(RoomContainer::room).toList();
-        } finally {
-            lock.readLock().unlock();
-        }
+    public Collection<Room> all(Node node) {
+        return containerRepository.findById(node.getIdentifier())
+                .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
+                .allRooms()
+                .stream().map(RoomContainer::getRoom).toList();
     }
 
     @Override
-    public Optional<Room> pickFree(Node node, Collection<User> users) {
-        lock.writeLock().lock();
-        try {
-            if (!node2roomsMap.containsKey(node)) {
-                throw new NodeNotFoundException("Node '" + node.getIdentifier() + "' does not exist");
-            }
-
-            Room room = null;
-            try {
-                room = pickerRepository.pick(node.getIdentifier(), users);
-            } catch (RuntimeException ignore) {} // TODO: may be problem. Check it later
-
-            Optional<RoomContainer> container = room == null?
-                    Optional.empty() :
-                    Optional.of(node2roomsMap.get(node).get(room.getIdentifier()));
-
-            if (container.isPresent()) {
-                var cont = container.get();
-                var addedUsers = userRepository.linkWithRoom(cont.room(), users, false);
-                cont.used().getAndAdd(addedUsers);
-            }
-
-            return container.map(RoomContainer::room);
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-
-    @Override
-    public void onCreateNode(Node node) {
-        lock.writeLock().lock();
-        try {
-            node2roomsMap.put(node, new Rooms());
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-
-    @Override
-    public List<Room> onRemoveNode(Node node) {
-        lock.writeLock().lock();
-        try {
-            var deleted = node2roomsMap.get(node).values().stream().map(container -> container.room).toList();
-            node2roomsMap.remove(node);
-
-            return deleted;
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-
-    private record RoomContainer(Room room, AtomicInteger used) {
-
-        public boolean isAvailable(int requiredSlots) {
-            return room.isAvailable(used.get(), requiredSlots);
-        }
+    public Room pick(Node node, Set<User> users) throws NoRoomsAvailableException {
+        return containerRepository.findById(node.getIdentifier())
+                .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
+                .pick(users);
     }
-
-    private static class Rooms extends LinkedHashMap<String, RoomContainer> {}
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java
index 63ee5ee..6ffdf9b 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java
@@ -1,207 +1,104 @@
 package ru.dragonestia.picker.repository.impl;
 
+import jakarta.annotation.PostConstruct;
 import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Repository;
+import org.springframework.stereotype.Component;
+import ru.dragonestia.picker.api.exception.NodeNotFoundException;
 import ru.dragonestia.picker.api.exception.RoomAreFullException;
+import ru.dragonestia.picker.api.exception.RoomNotFoundException;
 import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.User;
 import ru.dragonestia.picker.repository.UserRepository;
-import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
-import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-@Repository
+@Component
 @RequiredArgsConstructor
 public class UserRepositoryImpl implements UserRepository {
 
-    private final NodeId2PickerModeCache nodeId2PickerModeCache;
-    private final Map<User, Set<Room>> usersMap = new ConcurrentHashMap<>();
-    private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>();
-    private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
+    private final ContainerRepository containerRepository;
 
-    @Override
-    public int linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException {
-        var toAdd = new HashSet<User>();
-
-        lock.writeLock().lock();
-        try {
-            var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier());
-            var usersSet = roomUsers.getOrDefault(path, new HashSet<>());
+    private final Map<User, Set<Room>> userRooms = new ConcurrentHashMap<>();
 
-            if (!force && !room.hasUnlimitedSlots()) {
-                if (room.getMaxSlots() < usersSet.size() + users.size()) {
-                    throw new RoomAreFullException(room.getNodeIdentifier(), room.getIdentifier());
+    @PostConstruct
+    void init() {
+        containerRepository.setTransactionListener(transaction -> {
+            synchronized (userRooms) {
+                for (var user: transaction.target()) {
+                    var set = userRooms.computeIfAbsent(user, k -> new HashSet<>());
+                    set.add(transaction.room());
                 }
             }
+        });
+    }
 
-            users.forEach(user -> {
-                var set = usersMap.getOrDefault(user, new HashSet<>());
-                if (!set.contains(room)) {
-                    toAdd.add(user);
-                    set.add(room);
-                }
-                usersMap.put(user, set);
-            });
-
-            usersSet.addAll(toAdd);
-            roomUsers.put(path, usersSet);
+    @Override
+    public void linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException {
+        synchronized (userRooms) {
+            getRoomContainer(room).addUsers(users, force);
 
-            var picker = nodeId2PickerModeCache.get(room.getNodeIdentifier());
-            if (picker instanceof LeastPickedPicker leastPickedPicker) {
-                leastPickedPicker.updateUsersAmount(room, roomUsers.get(path).size());
+            for (var user: users) {
+                var set = userRooms.computeIfAbsent(user, k -> new HashSet<>());
+                set.add(room);
             }
-        } finally {
-            lock.writeLock().unlock();
         }
-        return toAdd.size();
     }
 
     @Override
     public void unlinkWithRoom(Room room, Collection<User> users) {
-        var counter = new AtomicInteger();
-
-        lock.writeLock().lock();
-        try {
-            usersMap.forEach((user, set) -> {
-                if (!set.contains(room)) return;
+        synchronized (userRooms) {
+            getRoomContainer(room).removeUsers(users);
 
+            for (var user: users) {
+                var set = userRooms.get(user);
+                if (set == null) continue;
                 set.remove(room);
-                counter.incrementAndGet();
-
-                if (set.isEmpty()) {
-                    usersMap.remove(user);
-                }
-            });
-
-            var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier());
-            var set = roomUsers.getOrDefault(path, new HashSet<>());
-            set.removeAll(users);
-            if (set.isEmpty()) {
-                roomUsers.remove(path);
-            } else {
-                roomUsers.put(path, set);
+                if (set.isEmpty()) userRooms.remove(user);
             }
-
-            var picker = nodeId2PickerModeCache.get(room.getNodeIdentifier());
-            if (picker instanceof LeastPickedPicker leastPickedPicker) {
-                leastPickedPicker.updateUsersAmount(room, set.size());
-            }
-        } finally {
-            lock.writeLock().unlock();
         }
-        counter.get();
     }
 
     @Override
-    public List<Room> findAllLinkedUserRooms(User user) {
-        lock.writeLock().lock();
-        try {
-            return usersMap.getOrDefault(user, new HashSet<>()).stream().toList();
-        } finally {
-            lock.writeLock().unlock();
-        }
+    public Collection<Room> findAllLinkedUserRooms(User user) {
+        return Collections.unmodifiableSet(userRooms.get(user));
     }
 
     @Override
-    public void onRemoveRoom(Room room) {
-        lock.writeLock().lock();
-        try {
-            var users = roomUsers.remove(new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()));
-
-            if (users == null) return;
-
-            users.forEach(user -> {
-                var set = usersMap.getOrDefault(user, new HashSet<>());
-                set.remove(room);
-
-                if (set.isEmpty()) {
-                    usersMap.remove(user);
-                }
-            });
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-
-    @Override
-    public List<User> usersOf(Room room) {
-        lock.readLock().lock();
-        try {
-            return roomUsers.getOrDefault(new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()), new HashSet<>())
-                    .stream()
-                    .toList();
-        } finally {
-            lock.readLock().unlock();
-        }
+    public Collection<User> usersOf(Room room) {
+        return getRoomContainer(room).allUsers();
     }
 
     @Override
-    public List<User> search(String input) {
-        lock.readLock().lock();
-        try {
-            return usersMap.keySet().stream()
-                    .filter(user -> user.getIdentifier().startsWith(input))
-                    .sorted(Comparator.comparing(User::getIdentifier))
-                    .toList();
-        } finally {
-            lock.readLock().unlock();
-        }
+    public Collection<User> search(String input) {
+        return userRooms.keySet().stream().filter(user -> user.getIdentifier().startsWith(input)).toList();
     }
 
     @Override
     public int countAllUsers() {
-        lock.readLock().lock();
-        try {
-            return usersMap.size();
-        } finally {
-            lock.readLock().unlock();
-        }
+        return userRooms.size();
     }
 
     @Override
     public Map<String, Integer> countUsersForNodes() {
-        var map = new HashMap<String, Set<User>>();
-
-        lock.readLock().lock();
-        try {
-            roomUsers.forEach((path, users) -> {
-                if (map.containsKey(path.node)) {
-                    map.get(path.node).addAll(users);
-                    return;
-                }
+        var result = new HashMap<String, Integer>();
 
-                map.put(path.node, new HashSet<>(users));
-            });
-        } finally {
-            lock.readLock().unlock();
-        }
+        containerRepository.all().forEach(nodeContainer -> {
+            var nodeId = nodeContainer.getNode().getIdentifier();
 
-        var result = new HashMap<String, Integer>();
-        map.forEach((node, users) -> result.put(node, users.size()));
+            nodeContainer.allRooms().forEach(roomContainer -> {
+                result.put(nodeId, result.getOrDefault(nodeId, 0) + roomContainer.countUsers());
+            });
+        });
 
         return result;
     }
 
-    private record NodeRoomPath(String node, String room) {
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(node, room);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == null) return false;
-            if (o == this) return true;
-            if (o instanceof NodeRoomPath other) {
-                return other.node().equals(node()) && other.room().equals(room());
-            }
-            return false;
-        }
+    private RoomContainer getRoomContainer(Room room) {
+        return containerRepository.findById(room.getNodeIdentifier())
+                .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()))
+                .findRoomById(room.getIdentifier())
+                .orElseThrow(() -> new RoomNotFoundException(room.getNodeIdentifier(), room.getIdentifier()));
     }
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java
deleted file mode 100644
index 4e1a005..0000000
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package ru.dragonestia.picker.repository.impl.cache;
-
-import org.springframework.stereotype.Component;
-import ru.dragonestia.picker.repository.impl.picker.RoomPicker;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Component
-public class NodeId2PickerModeCache {
-
-    private final Map<String, RoomPicker> cache = new ConcurrentHashMap<>();
-
-    public void put(String nodeId, RoomPicker picker) {
-        cache.put(nodeId, picker);
-    }
-
-    public void remove(String nodeId) {
-        cache.remove(nodeId);
-    }
-
-    public RoomPicker get(String nodeId) {
-        return cache.get(nodeId);
-    }
-}
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java
new file mode 100644
index 0000000..76716a2
--- /dev/null
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java
@@ -0,0 +1,107 @@
+package ru.dragonestia.picker.repository.impl.container;
+
+import lombok.Getter;
+import org.jetbrains.annotations.NotNull;
+import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
+import ru.dragonestia.picker.model.Node;
+import ru.dragonestia.picker.model.Room;
+import ru.dragonestia.picker.model.User;
+import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
+import ru.dragonestia.picker.repository.impl.picker.RoomPicker;
+import ru.dragonestia.picker.repository.impl.picker.RoundRobinPicker;
+import ru.dragonestia.picker.repository.impl.picker.SequentialFillingPicker;
+import ru.dragonestia.picker.repository.impl.type.UserTransaction;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class NodeContainer {
+
+    @Getter
+    private final Node node;
+    private final UserTransaction.Listener transactionListener;
+    private final RoomPicker picker;
+
+    private final ReadWriteLock roomLock = new ReentrantReadWriteLock();
+    private final Map<String, RoomContainer> rooms = new ConcurrentHashMap<>();
+
+    public NodeContainer(@NotNull Node node, @NotNull UserTransaction.Listener transactionListener) {
+        this.node = node;
+        this.transactionListener = transactionListener;
+        this.picker = initPicker();
+    }
+
+    private @NotNull RoomPicker initPicker() {
+        return switch (node.getPickingMethod()) {
+            case SEQUENTIAL_FILLING -> new SequentialFillingPicker(this);
+            case ROUND_ROBIN -> new RoundRobinPicker(this);
+            case LEAST_PICKED -> new LeastPickedPicker(this);
+        };
+    }
+
+    public void addRoom(Room room) throws RoomAlreadyExistException {
+        roomLock.writeLock().lock();
+        try {
+            if (rooms.containsKey(room.getIdentifier())) {
+                throw new RoomAlreadyExistException(node.getIdentifier(), room.getIdentifier());
+            }
+
+            var container = new RoomContainer(room, this);
+            rooms.put(room.getIdentifier(), container);
+            picker.add(container);
+        } finally {
+            roomLock.writeLock().unlock();
+        }
+    }
+
+    public void removeRoom(@NotNull Room room) {
+        roomLock.writeLock().lock();
+        try {
+            picker.remove(rooms.remove(room.getIdentifier()));
+        } finally {
+            roomLock.writeLock().unlock();
+        }
+    }
+
+    public void removeRoomsByIds(@NotNull Collection<String> roomIds) {
+        roomLock.writeLock().lock();
+        try {
+            roomIds.forEach(roomId -> picker.remove(rooms.remove(roomId)));
+        } finally {
+            roomLock.writeLock().unlock();
+        }
+    }
+
+    public @NotNull Optional<RoomContainer> findRoomById(@NotNull String roomId) {
+        roomLock.readLock().lock();
+        try {
+            return Optional.ofNullable(rooms.get(roomId));
+        } finally {
+            roomLock.readLock().unlock();
+        }
+    }
+
+    public @NotNull Collection<RoomContainer> allRooms() {
+        roomLock.readLock().lock();
+        try {
+            return rooms.values();
+        } finally {
+            roomLock.readLock().unlock();
+        }
+    }
+
+    public @NotNull Room pick(@NotNull Set<User> users) {
+        synchronized (picker) {
+            var room = picker.pick(users);
+            room.addUsers(users, false);
+            transactionListener.accept(new UserTransaction(room.getRoom(), users));
+            return room.getRoom();
+        }
+    }
+
+    public @NotNull RoomPicker getPicker() {
+        return picker;
+    }
+}
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java
new file mode 100644
index 0000000..6becbf4
--- /dev/null
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java
@@ -0,0 +1,106 @@
+package ru.dragonestia.picker.repository.impl.container;
+
+import lombok.Getter;
+import org.jetbrains.annotations.NotNull;
+import ru.dragonestia.picker.api.exception.RoomAreFullException;
+import ru.dragonestia.picker.model.Room;
+import ru.dragonestia.picker.model.User;
+import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class RoomContainer {
+
+    @Getter
+    private final Room room;
+    private final NodeContainer container;
+
+    private final ReadWriteLock usersLock = new ReentrantReadWriteLock(true);
+    private final Set<User> users = new HashSet<>();
+
+    public RoomContainer(@NotNull Room room, @NotNull NodeContainer container) {
+        this.room = room;
+        this.container = container;
+    }
+
+    public void addUsers(@NotNull Collection<User> toAdd, boolean force) {
+        usersLock.writeLock().lock();
+        try {
+            if (force || canAdd0(toAdd.size())) {
+                users.addAll(toAdd);
+                noticePickersAboutUserNumberUpdate();
+            } else {
+                throw new RoomAreFullException(room.getNodeIdentifier(), room.getIdentifier());
+            }
+        } finally {
+            usersLock.writeLock().unlock();
+        }
+    }
+
+    public void removeUsers(@NotNull Collection<User> toRemove) {
+        usersLock.writeLock().lock();
+        try {
+            users.removeAll(toRemove);
+            noticePickersAboutUserNumberUpdate();
+        } finally {
+            usersLock.writeLock().unlock();
+        }
+    }
+
+    public @NotNull Collection<User> removeAllUsers() {
+        usersLock.writeLock().lock();
+        try {
+            var set = new HashSet<>(users);
+            users.clear();
+            noticePickersAboutUserNumberUpdate();
+            return set;
+        } finally {
+            usersLock.writeLock().unlock();
+        }
+    }
+
+    public @NotNull Collection<User> allUsers() {
+        usersLock.readLock().lock();
+        try {
+            return Collections.unmodifiableSet(users);
+        } finally {
+            usersLock.readLock().unlock();
+        }
+    }
+
+    public int countUsers() {
+        return users.size();
+    }
+
+    private boolean canAdd0(int users) {
+        return room.hasUnlimitedSlots() || users + countUsers() <= room.getMaxSlots();
+    }
+
+    public boolean canAdd(int users) {
+        try {
+            return canAdd0(users);
+        } finally {
+            usersLock.readLock().unlock();
+        }
+    }
+
+    public boolean canBePicked(int users) {
+        usersLock.readLock().lock();
+        try {
+            return !room.isLocked() && canAdd0(users);
+        } finally {
+            usersLock.readLock().unlock();
+        }
+    }
+
+    private void noticePickersAboutUserNumberUpdate() {
+        if (container.getPicker() instanceof LeastPickedPicker picker) {
+            picker.updateUsersAmount(room, countUsers());
+        }
+    }
+}
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java
index 4a81985..aa6863c 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java
@@ -1,38 +1,39 @@
 package ru.dragonestia.picker.repository.impl.picker;
 
+import lombok.RequiredArgsConstructor;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.api.model.node.PickingMethod;
 import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.User;
 import ru.dragonestia.picker.repository.UserRepository;
 import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
+import ru.dragonestia.picker.repository.impl.container.NodeContainer;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
 import java.util.Collection;
 
+@RequiredArgsConstructor
 public class LeastPickedPicker implements RoomPicker {
 
-    private final UserRepository userRepository;
+    private final NodeContainer container;
     private final DynamicSortedMap<RoomWrapper> map = new DynamicSortedMap<>();
 
-    public LeastPickedPicker(UserRepository userRepository) {
-        this.userRepository = userRepository;
-    }
-
     @Override
-    public void add(Room room) {
+    public void add(RoomContainer container) {
         synchronized (map) {
-            map.put(new RoomWrapper(room, () -> userRepository.usersOf(room).size()));
+            map.put(new RoomWrapper(container));
         }
     }
 
     @Override
-    public void remove(Room room) {
+    public void remove(RoomContainer container) {
         synchronized (map) {
-            map.removeById(room.getIdentifier());
+            map.removeById(container.getRoom().getIdentifier());
         }
     }
 
     @Override
-    public Room pick(Collection<User> users) {
+    public RoomContainer pick(Collection<User> users) {
         RoomWrapper wrapper;
 
         synchronized (map) {
@@ -41,7 +42,7 @@ public Room pick(Collection<User> users) {
 
                 if (!wrapper.canAddUnits(users.size())) throw new RuntimeException();
             } catch (RuntimeException ex) {
-                throw new RuntimeException("There are no rooms available");
+                throw new NoRoomsAvailableException(container.getNode().getIdentifier());
             }
         }
 
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java
index b3df329..4e2d2a5 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java
@@ -1,10 +1,10 @@
 package ru.dragonestia.picker.repository.impl.picker;
 
 import ru.dragonestia.picker.api.model.node.PickingMethod;
-import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.User;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
-public interface RoomPicker extends Picker<Room, User> {
+public interface RoomPicker extends Picker<RoomContainer, User> {
 
     PickingMethod getPickingMode();
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java
index 1d0db9f..ba0dcc2 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java
@@ -1,46 +1,43 @@
 package ru.dragonestia.picker.repository.impl.picker;
 
-import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
 import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
-public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item, DynamicSortedMap.Item {
+public class RoomWrapper implements ItemWrapper<RoomContainer>, QueuedLinkedList.Item, DynamicSortedMap.Item {
 
-    private final Room room;
-    private final Supplier<Integer> userCountSupplier;
+    private final RoomContainer container;
     private Consumer<Integer> setter;
 
-    public RoomWrapper(Room room, Supplier<Integer> userCountSupplier) {
-        this.room = room;
-        this.userCountSupplier = userCountSupplier;
+    public RoomWrapper(RoomContainer container) {
+        this.container = container;
     }
 
     @Override
     public String getId() {
-        return room.getIdentifier();
+        return container.getRoom().getIdentifier();
     }
 
     @Override
     public int countUnits() {
-        return userCountSupplier.get();
+        return container.countUsers();
     }
 
     @Override
     public int maxUnits() {
-        return room.getMaxSlots();
+        return container.getRoom().getMaxSlots();
     }
 
     @Override
-    public Room getItem() {
-        return room;
+    public RoomContainer getItem() {
+        return container;
     }
 
     @Override
     public boolean canAddUnits(int amount) {
-        return ItemWrapper.super.canAddUnits(amount) && !room.isLocked();
+        return container.canBePicked(amount);
     }
 
     @Override
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java
index 3902619..f423cb0 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java
@@ -1,40 +1,39 @@
 package ru.dragonestia.picker.repository.impl.picker;
 
+import lombok.RequiredArgsConstructor;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.api.model.node.PickingMethod;
-import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.User;
-import ru.dragonestia.picker.repository.UserRepository;
 import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
+import ru.dragonestia.picker.repository.impl.container.NodeContainer;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
 import java.util.Collection;
 import java.util.concurrent.atomic.AtomicInteger;
 
+@RequiredArgsConstructor
 public class RoundRobinPicker implements RoomPicker {
 
-    private final UserRepository userRepository;
+    private final NodeContainer container;
     private final AtomicInteger addition = new AtomicInteger(0);
     private final QueuedLinkedList<RoomWrapper> list = new QueuedLinkedList<>(wrapper -> wrapper.canAddUnits(addition.get()));
 
-    public RoundRobinPicker(UserRepository userRepository) {
-        this.userRepository = userRepository;
-    }
-
     @Override
-    public void add(Room room) {
+    public void add(RoomContainer container) {
         synchronized (list) {
-            list.add(new RoomWrapper(room, () -> userRepository.usersOf(room).size()));
+            list.add(new RoomWrapper(container));
         }
     }
 
     @Override
-    public void remove(Room room) {
+    public void remove(RoomContainer container) {
         synchronized (list) {
-            list.removeById(room.getIdentifier());
+            list.removeById(container.getRoom().getIdentifier());
         }
     }
 
     @Override
-    public Room pick(Collection<User> users) {
+    public RoomContainer pick(Collection<User> users) {
         int amount = users.size();
         RoomWrapper wrapper;
 
@@ -43,7 +42,7 @@ public Room pick(Collection<User> users) {
                 addition.set(amount);
                 wrapper = list.pick();
             } catch (RuntimeException ex) {
-                throw new RuntimeException("There are no rooms available");
+                throw new NoRoomsAvailableException(container.getNode().getIdentifier());
             }
         }
 
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java
index 9cc221c..ca954f5 100644
--- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java
@@ -1,39 +1,38 @@
 package ru.dragonestia.picker.repository.impl.picker;
 
+import lombok.RequiredArgsConstructor;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.api.model.node.PickingMethod;
-import ru.dragonestia.picker.model.Room;
 import ru.dragonestia.picker.model.User;
-import ru.dragonestia.picker.repository.UserRepository;
+import ru.dragonestia.picker.repository.impl.container.NodeContainer;
+import ru.dragonestia.picker.repository.impl.container.RoomContainer;
 
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+@RequiredArgsConstructor
 public class SequentialFillingPicker implements RoomPicker {
 
-    private final UserRepository userRepository;
+    private final NodeContainer container;
     private final Map<String, RoomWrapper> wrappers = new LinkedHashMap<>();
 
-    public SequentialFillingPicker(UserRepository userRepository) {
-        this.userRepository = userRepository;
-    }
-
     @Override
-    public void add(Room room) {
+    public void add(RoomContainer container) {
         synchronized (wrappers) {
-            wrappers.put(room.getIdentifier(), new RoomWrapper(room, () -> userRepository.usersOf(room).size()));
+            wrappers.put(container.getRoom().getIdentifier(), new RoomWrapper(container));
         }
     }
 
     @Override
-    public void remove(Room room) {
+    public void remove(RoomContainer container) {
         synchronized (wrappers) {
-            wrappers.remove(room.getIdentifier());
+            wrappers.remove(container.getRoom().getIdentifier());
         }
     }
 
     @Override
-    public Room pick(Collection<User> users) {
+    public RoomContainer pick(Collection<User> users) {
         int amount = users.size();
 
         synchronized (wrappers) {
@@ -44,7 +43,7 @@ public Room pick(Collection<User> users) {
             }
         }
 
-        throw new RuntimeException("There are no rooms available");
+        throw new NoRoomsAvailableException(container.getNode().getIdentifier());
     }
 
     @Override
diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java
new file mode 100644
index 0000000..1ef9473
--- /dev/null
+++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java
@@ -0,0 +1,13 @@
+package ru.dragonestia.picker.repository.impl.type;
+
+import org.jetbrains.annotations.NotNull;
+import ru.dragonestia.picker.model.Room;
+import ru.dragonestia.picker.model.User;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+
+public record UserTransaction(@NotNull Room room, Collection<User> target) {
+
+    public interface Listener extends Consumer<UserTransaction> {}
+}
diff --git a/server/src/main/java/ru/dragonestia/picker/service/RoomService.java b/server/src/main/java/ru/dragonestia/picker/service/RoomService.java
index 23d39b7..780ee8e 100644
--- a/server/src/main/java/ru/dragonestia/picker/service/RoomService.java
+++ b/server/src/main/java/ru/dragonestia/picker/service/RoomService.java
@@ -9,6 +9,7 @@
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.model.User;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -21,11 +22,11 @@ public interface RoomService {
 
     Optional<Room> find(Node node, String roomId);
 
-    List<Room> all(Node node);
+    Collection<Room> all(Node node);
 
     List<ShortResponseRoom> getAllRoomsWithDetailsResponse(Node node, Set<RoomDetails> details);
 
-    PickedRoomResponse pickAvailable(Node node, List<User> users);
+    PickedRoomResponse pickAvailable(Node node, Set<User> users);
 
     void updateState(Room room);
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/service/UserService.java b/server/src/main/java/ru/dragonestia/picker/service/UserService.java
index be7644f..faafa0f 100644
--- a/server/src/main/java/ru/dragonestia/picker/service/UserService.java
+++ b/server/src/main/java/ru/dragonestia/picker/service/UserService.java
@@ -14,7 +14,7 @@
 
 public interface UserService {
 
-    List<Room> getUserRooms(User user);
+    Collection<Room> getUserRooms(User user);
 
     List<ShortResponseRoom> getUserRoomsWithDetails(User user, Set<RoomDetails> details);
 
@@ -22,7 +22,7 @@ public interface UserService {
 
     void unlinkUsersFromRoom(Room room, Collection<User> users);
 
-    List<User> getRoomUsers(Room room);
+    Collection<User> getRoomUsers(Room room);
 
     List<ResponseUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details);
 
diff --git a/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java b/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java
index 3ec93ed..7a4949a 100644
--- a/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java
+++ b/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java
@@ -8,6 +8,7 @@
 import ru.dragonestia.picker.api.model.node.ResponseNode;
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.repository.NodeRepository;
+import ru.dragonestia.picker.repository.RoomRepository;
 import ru.dragonestia.picker.service.NodeService;
 import ru.dragonestia.picker.storage.NodeAndRoomStorage;
 import ru.dragonestia.picker.util.DetailsExtractor;
@@ -23,22 +24,24 @@
 public class NodeServiceImpl implements NodeService {
 
     private final NodeRepository nodeRepository;
+    private final RoomRepository roomRepository;
     private final DetailsExtractor detailsExtractor;
     private final NamingValidator namingValidator;
     private final NodeAndRoomStorage storage;
 
     @Override
     public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException {
-        namingValidator.validateNodeId(node.getIdentifier());
         nodeRepository.create(node);
         storage.saveNode(node);
     }
 
     @Override
     public void remove(Node node) {
-        for (var room: nodeRepository.delete(node)) {
+        for (var room: roomRepository.all(node)) {
             storage.removeRoom(room);
         }
+
+        nodeRepository.delete(node);
         storage.removeNode(node);
     }
 
@@ -58,6 +61,6 @@ public List<ResponseNode> getAllNodesWithDetailsResponse(Set<NodeDetails> detail
 
     @Override
     public Optional<Node> find(String nodeId) {
-        return nodeRepository.find(nodeId);
+        return nodeRepository.findById(nodeId);
     }
 }
diff --git a/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java b/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java
index af05a0f..3d4a6b4 100644
--- a/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java
+++ b/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java
@@ -21,10 +21,7 @@
 import ru.dragonestia.picker.util.DetailsExtractor;
 import ru.dragonestia.picker.util.NamingValidator;
 
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Log4j2
@@ -43,7 +40,7 @@ public class RoomServiceImpl implements RoomService {
     public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException {
         namingValidator.validateRoomId(room.getNodeIdentifier(), room.getIdentifier());
 
-        var node = nodeRepository.find(room.getNodeIdentifier()).orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()));
+        var node = nodeRepository.findById(room.getNodeIdentifier()).orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()));
         if (!node.isPersist() && room.isPersist()) {
             throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier());
         }
@@ -64,7 +61,7 @@ public Optional<Room> find(Node node, String roomId) {
     }
 
     @Override
-    public List<Room> all(Node node) {
+    public Collection<Room> all(Node node) {
         return roomRepository.all(node);
     }
 
@@ -78,9 +75,8 @@ public List<ShortResponseRoom> getAllRoomsWithDetailsResponse(Node node, Set<Roo
     }
 
     @Override
-    public PickedRoomResponse pickAvailable(Node node, List<User> users) {
-        var room = roomRepository.pickFree(node, users)
-                .orElseThrow(() -> new RuntimeException("There are no rooms available"));
+    public PickedRoomResponse pickAvailable(Node node, Set<User> users) {
+        var room = roomRepository.pick(node, users);
         var roomUsers = userRepository.usersOf(room);
 
         return new PickedRoomResponse(
diff --git a/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java b/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java
index 0858493..d2848fa 100644
--- a/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java
+++ b/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java
@@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService {
     private final DetailsExtractor detailsExtractor;
 
     @Override
-    public List<Room> getUserRooms(User user) {
+    public Collection<Room> getUserRooms(User user) {
         return userRepository.findAllLinkedUserRooms(user);
     }
 
@@ -47,7 +47,7 @@ public void unlinkUsersFromRoom(Room room, Collection<User> users) {
     }
 
     @Override
-    public List<User> getRoomUsers(Room room) {
+    public Collection<User> getRoomUsers(Room room) {
         return userRepository.usersOf(room);
     }
 
diff --git a/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java b/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java
index d49c263..52783a2 100644
--- a/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java
+++ b/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java
@@ -8,6 +8,7 @@
 import ru.dragonestia.picker.api.util.IdentifierValidator;
 import ru.dragonestia.picker.model.User;
 
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -30,7 +31,7 @@ public boolean validateUserId(String input) {
         return IdentifierValidator.forUser(input);
     }
 
-    public List<User> validateUserIds(List<String> input) throws InvalidUsernamesException {
+    public void validateUserIds(Collection<String> input) throws InvalidUsernamesException {
         var users = new LinkedList<User>();
         var invalid = new LinkedList<String>();
 
@@ -44,9 +45,7 @@ public List<User> validateUserIds(List<String> input) throws InvalidUsernamesExc
         }
 
         if (!invalid.isEmpty()) {
-            throw new InvalidUsernamesException(input, invalid);
+            throw new InvalidUsernamesException(input.stream().toList(), invalid);
         }
-
-        return users;
     }
 }
diff --git a/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java b/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java
index b8df440..bbdbca6 100644
--- a/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java
+++ b/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java
@@ -10,6 +10,7 @@
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Import;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.config.FillingNodesConfig;
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.repository.RoomRepository;
@@ -40,14 +41,14 @@ public class LeastPickedTests {
     @ParameterizedTest
     @ArgumentsSource(PickingArgumentProvider.class)
     void testPicking(String expectedRoomId, int usersAmount) {
-        var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(usersAmount));
-        Assertions.assertTrue(roomOpt.isPresent());
+        var expectedRoomUsers = userRepository.usersOf(roomRepository.find(node, expectedRoomId).orElseThrow()).size();
 
-        var room = roomOpt.get();
+        var room = roomRepository.pick(node, userFiller.createRandomUsers(usersAmount));
         var slots = room.getMaxSlots();
         var users = userRepository.usersOf(room);
         Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation
 
+        System.out.printf("Room(%s) has %s/%s users. Expected: %s(%s), added: %s%n", room.getIdentifier(), users.size(), slots, expectedRoomId, expectedRoomUsers, usersAmount);
         Assertions.assertEquals(expectedRoomId, room.getIdentifier());
     }
 
@@ -73,7 +74,6 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext extensionCo
     @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
     @Test
     void testNoOneRoomExpected() { // Take 9 users. expected none result
-        var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(9));
-        Assertions.assertTrue(roomOpt.isEmpty());
+        Assertions.assertThrows(NoRoomsAvailableException.class, () -> roomRepository.pick(node, userFiller.createRandomUsers(9)));
     }
 }
diff --git a/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java b/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java
index ec23f94..5ab780d 100644
--- a/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java
+++ b/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java
@@ -8,7 +8,7 @@
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Import;
-import ru.dragonestia.picker.api.model.node.INode;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.config.FillingNodesConfig;
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.repository.RoomRepository;
@@ -39,10 +39,7 @@ public class RoundRobinTests {
     @ParameterizedTest
     @ArgumentsSource(PickingArgumentProvider.class)
     void testPicking(String expectedRoomId, int usersAmount) {
-        var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(usersAmount));
-        Assertions.assertTrue(roomOpt.isPresent());
-
-        var room = roomOpt.get();
+        var room = roomRepository.pick(node, userFiller.createRandomUsers(usersAmount));
         var slots = room.getMaxSlots();
         var users = userRepository.usersOf(room);
         Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation
@@ -69,7 +66,6 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext extensionCo
     @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
     @Test
     void testNoOneRoomExpected() { // Take 9 users. expected none result
-        var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(9));
-        Assertions.assertTrue(roomOpt.isEmpty());
+        Assertions.assertThrows(NoRoomsAvailableException.class, () -> roomRepository.pick(node, userFiller.createRandomUsers(9)));
     }
 }
diff --git a/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java b/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java
index 61fb5c8..4dde327 100644
--- a/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java
+++ b/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java
@@ -10,6 +10,7 @@
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Import;
+import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
 import ru.dragonestia.picker.config.FillingNodesConfig;
 import ru.dragonestia.picker.model.Node;
 import ru.dragonestia.picker.repository.RoomRepository;
@@ -40,14 +41,14 @@ public class SequentialFillingTests {
     @ParameterizedTest
     @ArgumentsSource(PickingArgumentProvider.class)
     void testPicking(String expectedRoomId, int usersAmount) {
-        var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(usersAmount));
-        Assertions.assertTrue(roomOpt.isPresent());
+        var expectedRoomUsers = userRepository.usersOf(roomRepository.find(node, expectedRoomId).orElseThrow()).size();
 
-        var room = roomOpt.get();
+        var room = roomRepository.pick(node, userFiller.createRandomUsers(usersAmount));
         var slots = room.getMaxSlots();
         var users = userRepository.usersOf(room);
         Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation
 
+        System.out.printf("Room(%s) has %s/%s users. Expected: %s(%s), added: %s%n", room.getIdentifier(), users.size(), slots, expectedRoomId, expectedRoomUsers, usersAmount);
         Assertions.assertEquals(expectedRoomId, room.getIdentifier());
     }
 
@@ -70,7 +71,6 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext extensionCo
     @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
     @Test
     void testNoOneRoomExpected() { // Take 9 users. expected none result
-        var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(9));
-        Assertions.assertTrue(roomOpt.isEmpty());
+        Assertions.assertThrows(NoRoomsAvailableException.class, () -> roomRepository.pick(node, userFiller.createRandomUsers(9)));
     }
 }
diff --git a/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java b/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java
index 304e87a..0968f54 100644
--- a/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java
+++ b/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java
@@ -21,6 +21,7 @@
 import ru.dragonestia.picker.model.type.SlotLimit;
 
 import java.util.List;
+import java.util.Set;
 
 @SpringBootTest
 public class RoomServiceTests {
@@ -93,7 +94,7 @@ void test_pickRoom() {
 
         rooms.forEach(room -> roomService.create(room));
 
-        var users = List.of(
+        var users = Set.of(
                 new User(UserIdentifier.of("1")),
                 new User(UserIdentifier.of("2")),
                 new User(UserIdentifier.of("3")),
@@ -121,6 +122,6 @@ void test_nodeDoesNotExists() {
         Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.create(room));
         Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.remove(room));
         Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.find(node, "Bruh"));
-        Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.pickAvailable(node, List.of(new User(UserIdentifier.of("1")))));
+        Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.pickAvailable(node, Set.of(new User(UserIdentifier.of("1")))));
     }
 }
diff --git a/server/src/test/java/ru/dragonestia/picker/util/UserFiller.java b/server/src/test/java/ru/dragonestia/picker/util/UserFiller.java
index 0c17815..1f6f53f 100644
--- a/server/src/test/java/ru/dragonestia/picker/util/UserFiller.java
+++ b/server/src/test/java/ru/dragonestia/picker/util/UserFiller.java
@@ -4,18 +4,16 @@
 import ru.dragonestia.picker.api.repository.type.UserIdentifier;
 import ru.dragonestia.picker.model.User;
 
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
 
 @TestComponent
 public class UserFiller {
 
-    public List<User> createRandomUsers(int amount) {
-        var list = new LinkedList<User>();
+    public Set<User> createRandomUsers(int amount) {
+        var set = new HashSet<User>();
         for (int i = 0; i < amount; i++) {
-            list.add(new User(UserIdentifier.of(UUID.randomUUID().toString())));
+            set.add(new User(UserIdentifier.of(UUID.randomUUID().toString())));
         }
-        return list;
+        return set;
     }
 }
diff --git a/settings.gradle b/settings.gradle
index 0762c33..0c75b03 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,4 +4,5 @@ include 'server'
 include 'control-panel'
 include 'client-api'
 include 'client-impl'
+include 'noiser'