diff --git a/.gitignore b/.gitignore index 77fbb7e..e333297 100755 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ target .classpath .project .claude +.vscode +.DS_Store +*.log +plans \ No newline at end of file diff --git a/pom.xml b/pom.xml index addbfe1..927a3bb 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ false - cz.kamma.fabka.httpserver.HttpServerApplication + cz.kamma.fabka.app.HttpServerApplication diff --git a/src/main/java/cz/kamma/fabka/repository/ChatRepository.java b/src/main/java/cz/kamma/fabka/repository/ChatRepository.java index 9faf84a..6e78c3c 100644 --- a/src/main/java/cz/kamma/fabka/repository/ChatRepository.java +++ b/src/main/java/cz/kamma/fabka/repository/ChatRepository.java @@ -9,6 +9,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import cz.kamma.fabka.repository.model.ChatLine; import cz.kamma.fabka.repository.model.ChatVoteStats; @@ -83,23 +84,26 @@ public class ChatRepository { if (chatId <= 0) { return new ChatVoteStats("", ""); } + long startTime = System.nanoTime(); + ChatVoteStats result = null; try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(CHAT_VOTES_USERS_SQL)) { ps.setLong(1, chatId); ps.setLong(2, chatId); try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) { - return new ChatVoteStats("", ""); + if (rs.next()) { + result = new ChatVoteStats( + valueOrDefault(rs.getString("thumbup"), ""), + valueOrDefault(rs.getString("thumbdown"), "") + ); } - return new ChatVoteStats( - valueOrDefault(rs.getString("thumbup"), ""), - valueOrDefault(rs.getString("thumbdown"), "") - ); } } catch (Exception ex) { ex.printStackTrace(); - return new ChatVoteStats("", ""); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] CHAT_VOTES_USERS_SQL: chatId=" + chatId + ", duration=" + durationMs + "ms"); + return result != null ? result : new ChatVoteStats("", ""); } public void addChatMessage(long userId, String message) { @@ -123,6 +127,7 @@ public class ChatRepository { return lines; } int safeLimit = limit <= 0 ? 40 : limit; + long startTime = System.nanoTime(); try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(CHAT_LINES_SQL)) { ps.setLong(1, currentUserId); @@ -146,6 +151,8 @@ public class ChatRepository { } catch (Exception ex) { ex.printStackTrace(); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] CHAT_LINES_SQL: userId=" + currentUserId + ", limit=" + safeLimit + ", lines=" + lines.size() + ", duration=" + durationMs + "ms"); return lines; } diff --git a/src/main/java/cz/kamma/fabka/repository/ForumRepository.java b/src/main/java/cz/kamma/fabka/repository/ForumRepository.java index ccb4569..eca52c7 100644 --- a/src/main/java/cz/kamma/fabka/repository/ForumRepository.java +++ b/src/main/java/cz/kamma/fabka/repository/ForumRepository.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import cz.kamma.fabka.repository.model.AttachmentData; import cz.kamma.fabka.repository.model.ForumAttachment; @@ -30,6 +31,16 @@ public class ForumRepository { private static final ZoneId APP_ZONE = ZoneId.of("Europe/Prague"); private static final String FORUM_DETAIL_SQL = "SELECT id, name, description, countdown FROM forum WHERE id=? AND active=1 LIMIT 1"; + private static final String FORUM_MESSAGES_SQL_DEBUG = + "SELECT fi.id, fi.text, fi.created, fi.quoteitem, fi.sticky, ua.id AS author_id, ua.username, ua.created AS author_created, ua.city, " + + " (SELECT COUNT(*) FROM forum_items fi2 WHERE fi2.createdby=ua.id AND fi2.deleted=0) AS author_posts, " + + " COALESCE((SELECT SUM(votevalue) FROM voting v WHERE v.forumitemid=fi.id), 0) AS vvalue, " + + " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes, " + + " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no, " + + " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes_users, " + + " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no_users " + + "FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby " + + "WHERE fi.forumid=? AND fi.deleted=0 ORDER BY fi.created DESC"; private static final String QUOTED_ITEM_SQL = "SELECT fi.text, ua.username FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby WHERE fi.id=? LIMIT 1"; private static final String ATTACHMENTS_SQL = @@ -104,6 +115,7 @@ public class ForumRepository { public List listActiveForums() { List forums = new ArrayList<>(); + long startTime = System.nanoTime(); try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(FORUM_SQL); ResultSet rs = ps.executeQuery()) { @@ -122,6 +134,8 @@ public class ForumRepository { } catch (Exception ex) { ex.printStackTrace(); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] FORUM_SQL: forums=" + forums.size() + ", duration=" + durationMs + "ms"); return forums; } @@ -144,6 +158,7 @@ public class ForumRepository { if (userId <= 0) { return result; } + long startTime = System.nanoTime(); try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(NEW_MESSAGES_COUNT_SQL)) { ps.setLong(1, userId); @@ -159,6 +174,8 @@ public class ForumRepository { } catch (Exception ex) { ex.printStackTrace(); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] NEW_MESSAGES_COUNT_SQL: userId=" + userId + ", forums=" + result.size() + ", duration=" + durationMs + "ms"); return result; } @@ -166,24 +183,27 @@ public class ForumRepository { if (forumId <= 0) { return null; } + long startTime = System.nanoTime(); + ForumDetail result = null; try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(FORUM_DETAIL_SQL)) { ps.setLong(1, forumId); try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) { - return null; + if (rs.next()) { + result = new ForumDetail( + rs.getLong("id"), + rs.getString("name"), + rs.getString("description"), + rs.getString("countdown") + ); } - return new ForumDetail( - rs.getLong("id"), - rs.getString("name"), - rs.getString("description"), - rs.getString("countdown") - ); } } catch (Exception ex) { ex.printStackTrace(); - return null; } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] FORUM_DETAIL_SQL: forumId=" + forumId + ", duration=" + durationMs + "ms"); + return result; } public List listMessagesByForumId(long forumId) { @@ -191,46 +211,103 @@ public class ForumRepository { if (forumId <= 0) { return messages; } - if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL)) { + long startTime = System.nanoTime(); + if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL_DEBUG)) { loadMessages(messages, forumId, FORUM_MESSAGES_SQL_NO_STICKY); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] FORUM_MESSAGES_SQL: forumId=" + forumId + ", rows=" + messages.size() + ", duration=" + durationMs + "ms"); return messages; } private boolean loadMessages(List out, long forumId, String sql) { out.clear(); + long queryStartTime = System.nanoTime(); + + // First, fetch all messages and collect item IDs for batch attachment loading + List itemIds = new ArrayList<>(); + List createdTimes = new ArrayList<>(); + List authorIds = new ArrayList<>(); + List usernames = new ArrayList<>(); + List cities = new ArrayList<>(); + List texts = new ArrayList<>(); + List authorPostCounts = new ArrayList<>(); + List vvalues = new ArrayList<>(); + List voteYes = new ArrayList<>(); + List voteNo = new ArrayList<>(); + List voteYesUsers = new ArrayList<>(); + List voteNoUsers = new ArrayList<>(); + List ids = new ArrayList<>(); + List quoteItemIds = new ArrayList<>(); + List stickies = new ArrayList<>(); + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setLong(1, forumId); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { - Timestamp createdTs = rs.getTimestamp("created"); - long quoteItemId = rs.getLong("quoteitem"); - QuotedTextItem quotedItem = quoteItemId > 0 ? findQuotedItem(quoteItemId) : null; - out.add(new ForumMessage( - rs.getLong("id"), - rs.getLong("author_id"), - valueOrDefault(rs.getString("username"), "N/A"), - formatTs(rs.getTimestamp("author_created")), - valueOrDefault(rs.getString("city"), ""), - rs.getLong("author_posts"), - createdTs == null ? 0L : createdTs.getTime(), - rs.getInt("vvalue"), - rs.getInt("vote_yes"), - rs.getInt("vote_no"), - valueOrDefault(rs.getString("vote_yes_users"), ""), - valueOrDefault(rs.getString("vote_no_users"), ""), - quoteItemId, - quotedItem, - formatTs(createdTs), - valueOrDefault(rs.getString("text"), ""), - listAttachmentsByForumItemId(rs.getLong("id")), - rs.getInt("sticky") == 1 - )); + ids.add(rs.getLong("id")); + itemIds.add(rs.getLong("id")); + createdTimes.add(rs.getTimestamp("created")); + authorIds.add(rs.getLong("author_id")); + usernames.add(rs.getString("username")); + cities.add(rs.getString("city")); + authorPostCounts.add(rs.getLong("author_posts")); + texts.add(rs.getString("text")); + vvalues.add(rs.getInt("vvalue")); + voteYes.add(rs.getInt("vote_yes")); + voteNo.add(rs.getInt("vote_no")); + voteYesUsers.add(rs.getString("vote_yes_users")); + voteNoUsers.add(rs.getString("vote_no_users")); + quoteItemIds.add(rs.getLong("quoteitem")); + stickies.add(rs.getInt("sticky")); } } + + // Batch load attachments for all items + long attachmentsStartTime = System.nanoTime(); + Map> attachmentsByItemId = new LinkedHashMap<>(); + if (!itemIds.isEmpty()) { + attachmentsByItemId = loadAttachmentsBatch(itemIds); + } + long attachmentsDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - attachmentsStartTime); + System.out.println("[DEBUG] Batch attachment load: " + attachmentsDurationMs + "ms, " + itemIds.size() + " items"); + + // Build message objects + for (int i = 0; i < ids.size(); i++) { + long messageId = ids.get(i); + long quoteItemId = quoteItemIds.get(i); + QuotedTextItem quotedItem = quoteItemId > 0 ? findQuotedItem(quoteItemId) : null; + List attachments = attachmentsByItemId.getOrDefault(messageId, new ArrayList<>()); + + out.add(new ForumMessage( + messageId, + authorIds.get(i), + valueOrDefault(usernames.get(i), "N/A"), + formatTs(createdTimes.get(i)), + valueOrDefault(cities.get(i), ""), + authorPostCounts.get(i), + createdTimes.get(i) == null ? 0L : createdTimes.get(i).getTime(), + vvalues.get(i), + voteYes.get(i), + voteNo.get(i), + valueOrDefault(voteYesUsers.get(i), ""), + valueOrDefault(voteNoUsers.get(i), ""), + quoteItemId, + quotedItem, + formatTs(createdTimes.get(i)), + valueOrDefault(texts.get(i), ""), + attachments, + stickies.get(i) == 1 + )); + } + + long totalDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - queryStartTime); + System.out.println("[DEBUG] loadMessages completed: total=" + totalDurationMs + "ms, rows=" + out.size()); return true; } catch (Exception ex) { + System.err.println("[DEBUG] loadMessages ERROR: " + ex.getMessage()); + ex.printStackTrace(); return false; } } @@ -374,22 +451,25 @@ public class ForumRepository { if (messageId <= 0) { return null; } + long startTime = System.nanoTime(); + QuotedTextItem result = null; try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(QUOTED_ITEM_SQL)) { ps.setLong(1, messageId); try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) { - return null; + if (rs.next()) { + result = new QuotedTextItem( + valueOrDefault(rs.getString("username"), "N/A"), + valueOrDefault(rs.getString("text"), "") + ); } - return new QuotedTextItem( - valueOrDefault(rs.getString("username"), "N/A"), - valueOrDefault(rs.getString("text"), "") - ); } } catch (Exception ex) { ex.printStackTrace(); - return null; } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] QUOTED_ITEM_SQL: messageId=" + messageId + ", duration=" + durationMs + "ms"); + return result; } public long addReply(long forumId, long userId, String message, Long quoteItem) { @@ -509,6 +589,7 @@ public class ForumRepository { if (forumItemId <= 0) { return out; } + long startTime = System.nanoTime(); try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(ATTACHMENTS_SQL)) { ps.setLong(1, forumItemId); @@ -525,6 +606,10 @@ public class ForumRepository { } catch (Exception ex) { ex.printStackTrace(); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + if (durationMs > 1) { + System.out.println("[DEBUG] ATTACHMENTS_SQL: forumItemId=" + forumItemId + ", count=" + out.size() + ", duration=" + durationMs + "ms"); + } return out; } @@ -559,6 +644,36 @@ public class ForumRepository { } } + private Map> loadAttachmentsBatch(List itemIds) { + Map> result = new LinkedHashMap<>(); + if (itemIds.isEmpty()) { + return result; + } + String sql = "SELECT forumitemsid, id, name, ispicture, width, size FROM attachments WHERE forumitemsid IN (" + + String.join(",", java.util.Collections.nCopies(itemIds.size(), "?")) + ") ORDER BY forumitemsid, id"; + try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); + PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 0; i < itemIds.size(); i++) { + ps.setLong(i + 1, itemIds.get(i)); + } + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + long forumItemId = rs.getLong("forumitemsid"); + ForumAttachment attachment = new ForumAttachment( + rs.getLong("id"), + valueOrDefault(rs.getString("name"), "attachment"), + rs.getInt("ispicture") == 1, + rs.getInt("width") + ); + result.computeIfAbsent(forumItemId, k -> new ArrayList<>()).add(attachment); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return result; + } + private static String valueOrDefault(String value, String defaultValue) { return value == null || value.isBlank() ? defaultValue : value; } diff --git a/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java b/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java index a0eeb9a..24846d5 100644 --- a/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java +++ b/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java @@ -10,6 +10,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import cz.kamma.fabka.repository.model.PrivateMessageItem; import cz.kamma.fabka.repository.model.PrivateMessageStats; @@ -56,6 +57,7 @@ public class PrivateMessageRepository { "where (m.to_user=? or m.from_user=?) and m.deleted=0 and m.reply_to is null " + "order by " + order; + long startTime = System.nanoTime(); try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setLong(1, userId); @@ -77,6 +79,8 @@ public class PrivateMessageRepository { } catch (Exception ex) { ex.printStackTrace(); } + long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + System.out.println("[DEBUG] PM listThreads: userId=" + userId + ", threads=" + out.size() + ", duration=" + durationMs + "ms"); return out; }