From 5319e5e5c34137329d7c661f9e16147c725c97c9 Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 26 Mar 2026 09:41:31 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Database=20performance=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. HabboManager - O(1) username lookup Before: getHabbo(String) held a synchronized lock and iterated ALL online users every call After: Secondary ConcurrentHashMap keyed by lowercase username → instant get() lookup, no lock contention 2. ItemsComponent - Batch DB saves Before: dispose() spawned a separate thread per dirty item, each opening its own DB connection After: Single connection, JDBC addBatch()/executeBatch() for both UPDATE and DELETE, flushed every 100 items. A user with 500 dirty items now does 5 batch executions instead of 500 thread spawns + 500 connections. 3. AcceptFriendRequestEvent - N+1 elimination Before: For each offline user: query 1 (getOfflineHabboInfo by ID) → query 2 (load full Habbo by username) = 2 queries × up to 100 users = 200 queries After: Single query by user ID directly = 1 query × up to 100 users = 100 queries (50% reduction) 4. RoomManager - Direct HashMap lookup Before: getCategory(int id) iterated all categories checking getId() == id even though the map is already keyed by ID After: Direct roomCategories.get(id) → O(1) instead of O(n) --- .../habbo/habbohotel/rooms/RoomManager.java | 18 +----- .../habbo/habbohotel/users/HabboManager.java | 21 +++---- .../users/inventory/ItemsComponent.java | 63 ++++++++++++++++--- .../friends/AcceptFriendRequestEvent.java | 16 +---- 4 files changed, 70 insertions(+), 48 deletions(-) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java index 6348ca40..b44f0723 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java @@ -179,12 +179,7 @@ public class RoomManager { } public RoomCategory getCategory(int id) { - for (RoomCategory category : this.roomCategories.values()) { - if (category.getId() == id) - return category; - } - - return null; + return this.roomCategories.get(id); } public RoomCategory getCategory(String name) { @@ -220,15 +215,8 @@ public class RoomManager { } public boolean hasCategory(int categoryId, Habbo habbo) { - for (RoomCategory category : this.roomCategories.values()) { - if (category.getId() == categoryId) { - if (category.getMinRank() <= habbo.getHabboInfo().getRank().getId()) { - return true; - } - } - } - - return false; + RoomCategory category = this.roomCategories.get(categoryId); + return category != null && category.getMinRank() <= habbo.getHabboInfo().getRank().getId(); } public THashMap getRoomCategories() { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java index af9f7368..1ed19667 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java @@ -35,11 +35,13 @@ public class HabboManager { public static boolean NAMECHANGE_ENABLED = false; private final ConcurrentHashMap onlineHabbos; + private final ConcurrentHashMap onlineHabbosByName; public HabboManager() { long millis = System.currentTimeMillis(); this.onlineHabbos = new ConcurrentHashMap<>(); + this.onlineHabbosByName = new ConcurrentHashMap<>(); LOGGER.info("Habbo Manager -> Loaded! ({} MS)", System.currentTimeMillis() - millis); } @@ -80,10 +82,12 @@ public class HabboManager { public void addHabbo(Habbo habbo) { this.onlineHabbos.put(habbo.getHabboInfo().getId(), habbo); + this.onlineHabbosByName.put(habbo.getHabboInfo().getUsername().toLowerCase(), habbo); } public void removeHabbo(Habbo habbo) { this.onlineHabbos.remove(habbo.getHabboInfo().getId()); + this.onlineHabbosByName.remove(habbo.getHabboInfo().getUsername().toLowerCase()); } public Habbo getHabbo(int id) { @@ -91,14 +95,7 @@ public class HabboManager { } public Habbo getHabbo(String username) { - synchronized (this.onlineHabbos) { - for (Map.Entry map : this.onlineHabbos.entrySet()) { - if (map.getValue().getHabboInfo().getUsername().equalsIgnoreCase(username)) - return map.getValue(); - } - } - - return null; + return this.onlineHabbosByName.get(username.toLowerCase()); } public Habbo loadHabbo(String sso) { @@ -178,11 +175,9 @@ public class HabboManager { } public void sendPacketToHabbosWithPermission(ServerMessage message, String perm) { - synchronized (this.onlineHabbos) { - for (Habbo habbo : this.onlineHabbos.values()) { - if (habbo.hasPermission(perm)) { - habbo.getClient().sendResponse(message); - } + for (Habbo habbo : this.onlineHabbos.values()) { + if (habbo.hasPermission(perm)) { + habbo.getClient().sendResponse(message); } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/inventory/ItemsComponent.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/inventory/ItemsComponent.java index 7a49a166..9589a892 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/inventory/ItemsComponent.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/inventory/ItemsComponent.java @@ -159,14 +159,63 @@ public class ItemsComponent { } if (!this.items.isEmpty()) { - for (int i = this.items.size(); i-- > 0; ) { - try { - items.advance(); - } catch (NoSuchElementException e) { - break; + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) { + try (PreparedStatement updateStmt = connection.prepareStatement( + "UPDATE items SET user_id = ?, room_id = ?, wall_pos = ?, x = ?, y = ?, z = ?, rot = ?, extra_data = ?, limited_data = ? WHERE id = ?")) { + try (PreparedStatement deleteStmt = connection.prepareStatement( + "DELETE FROM items WHERE id = ?")) { + + int updateCount = 0; + int deleteCount = 0; + + for (int i = this.items.size(); i-- > 0; ) { + try { + items.advance(); + } catch (NoSuchElementException e) { + break; + } + + HabboItem item = items.value(); + if (item.needsDelete()) { + deleteStmt.setInt(1, item.getId()); + deleteStmt.addBatch(); + deleteCount++; + item.needsUpdate(false); + item.needsDelete(false); + } else if (item.needsUpdate()) { + updateStmt.setInt(1, item.getUserId()); + updateStmt.setInt(2, item.getRoomId()); + updateStmt.setString(3, item.getWallPosition()); + updateStmt.setInt(4, item.getX()); + updateStmt.setInt(5, item.getY()); + updateStmt.setDouble(6, item.getZ()); + updateStmt.setInt(7, item.getRotation()); + updateStmt.setString(8, item.getExtradata()); + updateStmt.setString(9, item.getLimitedStack() + ":" + item.getLimitedSells()); + updateStmt.setInt(10, item.getId()); + updateStmt.addBatch(); + updateCount++; + item.needsUpdate(false); + } + + if (updateCount > 0 && updateCount % 100 == 0) { + updateStmt.executeBatch(); + } + if (deleteCount > 0 && deleteCount % 100 == 0) { + deleteStmt.executeBatch(); + } + } + + if (deleteCount % 100 != 0) { + deleteStmt.executeBatch(); + } + if (updateCount % 100 != 0) { + updateStmt.executeBatch(); + } + } } - if (items.value().needsUpdate()) - Emulator.getThreading().run(items.value()); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception during batch item save", e); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/AcceptFriendRequestEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/AcceptFriendRequestEvent.java index 869fe532..6017b08d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/AcceptFriendRequestEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/AcceptFriendRequestEvent.java @@ -3,7 +3,6 @@ package com.eu.habbo.messages.incoming.friends; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.messenger.Messenger; import com.eu.habbo.habbohotel.users.Habbo; -import com.eu.habbo.habbohotel.users.HabboInfo; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.friends.FriendRequestErrorComposer; import org.slf4j.Logger; @@ -14,7 +13,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import static com.eu.habbo.habbohotel.users.HabboManager.getOfflineHabboInfo; public class AcceptFriendRequestEvent extends MessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(AcceptFriendRequestEvent.class); @@ -44,18 +42,10 @@ public class AcceptFriendRequestEvent extends MessageHandler { Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); if(target == null) { - HabboInfo habboInfo = getOfflineHabboInfo(userId); - - if(habboInfo == null) { - this.client.sendResponse(new FriendRequestErrorComposer(FriendRequestErrorComposer.TARGET_NOT_FOUND)); - this.client.getHabbo().getMessenger().deleteFriendRequests(userId, this.client.getHabbo().getHabboInfo().getId()); - continue; - } - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT users.*, users_settings.block_friendrequests FROM users INNER JOIN users_settings ON users.id = users_settings.user_id WHERE username = ? LIMIT 1")) { - statement.setString(1, habboInfo.getUsername()); + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT users.*, users_settings.block_friendrequests FROM users INNER JOIN users_settings ON users.id = users_settings.user_id WHERE users.id = ? LIMIT 1")) { + statement.setInt(1, userId); try (ResultSet set = statement.executeQuery()) { - while (set.next()) { + if (set.next()) { target = new Habbo(set); } }