xptracker: limit how often the same players are looked up

Use player rank to set how often the same player is allowed to be looked
up. Replace the ConcurrentLinkedDeque with a synchronized ArrayDeque
which has a constant time size().
This commit is contained in:
Adam
2019-02-15 21:04:42 -05:00
parent a0cdd64719
commit bc0ec093dc
3 changed files with 89 additions and 26 deletions

View File

@@ -29,8 +29,8 @@ import com.google.common.hash.Funnels;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.hiscore.HiscoreEndpoint; import net.runelite.http.api.hiscore.HiscoreEndpoint;
@@ -50,8 +50,8 @@ import org.sql2o.Sql2o;
@Slf4j @Slf4j
public class XpTrackerService public class XpTrackerService
{ {
private static final int QUEUE_LIMIT = 100_000; private static final int QUEUE_LIMIT = 32768;
private static final Duration UPDATE_TIME = Duration.ofMinutes(5); private static final int BLOOMFILTER_EXPECTED_INSERTIONS = 100_000;
@Autowired @Autowired
@Qualifier("Runelite XP Tracker SQL2O") @Qualifier("Runelite XP Tracker SQL2O")
@@ -60,7 +60,7 @@ public class XpTrackerService
@Autowired @Autowired
private HiscoreService hiscoreService; private HiscoreService hiscoreService;
private final Queue<String> usernameUpdateQueue = new ConcurrentLinkedDeque<>(); private final Queue<String> usernameUpdateQueue = new ArrayDeque<>();
private BloomFilter<String> usernameFilter = createFilter(); private BloomFilter<String> usernameFilter = createFilter();
public void update(String username) throws ExecutionException public void update(String username) throws ExecutionException
@@ -76,13 +76,37 @@ public class XpTrackerService
return; return;
} }
if (usernameUpdateQueue.size() >= QUEUE_LIMIT) try (Connection con = sql2o.open())
{ {
log.warn("Username update queue is full ({})", QUEUE_LIMIT); PlayerEntity playerEntity = findOrCreatePlayer(con, username);
return; Duration frequency = updateFrequency(playerEntity);
Instant now = Instant.now();
Duration timeSinceLastUpdate = Duration.between(playerEntity.getLast_updated(), now);
if (timeSinceLastUpdate.toMillis() < frequency.toMillis())
{
log.debug("User {} updated too recently", username);
usernameFilter.put(username);
return;
}
synchronized (usernameUpdateQueue)
{
if (usernameUpdateQueue.size() >= QUEUE_LIMIT)
{
log.warn("Username update queue is full ({})", QUEUE_LIMIT);
return;
}
}
con.createQuery("update player set last_updated = CURRENT_TIMESTAMP where id = :id")
.addParameter("id", playerEntity.getId())
.executeUpdate();
} }
usernameUpdateQueue.add(username); synchronized (usernameUpdateQueue)
{
usernameUpdateQueue.add(username);
}
usernameFilter.put(username); usernameFilter.put(username);
} }
@@ -104,13 +128,6 @@ public class XpTrackerService
log.debug("Hiscore for {} already up to date", username); log.debug("Hiscore for {} already up to date", username);
return; return;
} }
Duration difference = Duration.between(currentXp.getTime(), now);
if (difference.compareTo(UPDATE_TIME) <= 0)
{
log.debug("Updated {} too recently", username);
return;
}
} }
con.createQuery("insert into xp (player,attack_xp,defence_xp,strength_xp,hitpoints_xp,ranged_xp,prayer_xp,magic_xp,cooking_xp,woodcutting_xp," con.createQuery("insert into xp (player,attack_xp,defence_xp,strength_xp,hitpoints_xp,ranged_xp,prayer_xp,magic_xp,cooking_xp,woodcutting_xp,"
@@ -172,6 +189,11 @@ public class XpTrackerService
.addParameter("construction_rank", hiscoreResult.getConstruction().getRank()) .addParameter("construction_rank", hiscoreResult.getConstruction().getRank())
.addParameter("overall_rank", hiscoreResult.getOverall().getRank()) .addParameter("overall_rank", hiscoreResult.getOverall().getRank())
.executeUpdate(); .executeUpdate();
con.createQuery("update player set rank = :rank where id = :id")
.addParameter("id", playerEntity.getId())
.addParameter("rank", hiscoreResult.getOverall().getRank())
.executeUpdate();
} }
} }
@@ -197,6 +219,7 @@ public class XpTrackerService
playerEntity.setId(id); playerEntity.setId(id);
playerEntity.setName(username); playerEntity.setName(username);
playerEntity.setTracked_since(now); playerEntity.setTracked_since(now);
playerEntity.setLast_updated(now);
return playerEntity; return playerEntity;
} }
@@ -220,18 +243,21 @@ public class XpTrackerService
@Scheduled(fixedDelay = 1000) @Scheduled(fixedDelay = 1000)
public void update() throws ExecutionException public void update() throws ExecutionException
{ {
String next = usernameUpdateQueue.poll(); String next;
synchronized (usernameUpdateQueue)
{
next = usernameUpdateQueue.poll();
}
if (next == null) if (next == null)
{ {
return; return;
} }
HiscoreResult hiscoreResult = hiscoreService.lookupUsername(next, HiscoreEndpoint.NORMAL); update(next);
update(next, hiscoreResult);
} }
@Scheduled(fixedDelay = 3 * 60 * 60 * 1000) // 3 hours @Scheduled(fixedDelay = 6 * 60 * 60 * 1000) // 6 hours
public void clearFilter() public void clearFilter()
{ {
usernameFilter = createFilter(); usernameFilter = createFilter();
@@ -241,14 +267,47 @@ public class XpTrackerService
{ {
final BloomFilter<String> filter = BloomFilter.create( final BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()), Funnels.stringFunnel(Charset.defaultCharset()),
100_000 BLOOMFILTER_EXPECTED_INSERTIONS
); );
for (String toUpdate : usernameUpdateQueue) synchronized (usernameUpdateQueue)
{ {
filter.put(toUpdate); for (String toUpdate : usernameUpdateQueue)
{
filter.put(toUpdate);
}
} }
return filter; return filter;
} }
/**
* scale how often to check hiscore updates for players based on their rank
* @param playerEntity
* @return
*/
private static Duration updateFrequency(PlayerEntity playerEntity)
{
Integer rank = playerEntity.getRank();
if (rank == null)
{
return Duration.ofDays(7);
}
else if (rank < 10_000)
{
return Duration.ofHours(6);
}
else if (rank < 50_000)
{
return Duration.ofDays(2);
}
else if (rank < 100_000)
{
return Duration.ofDays(5);
}
else
{
return Duration.ofDays(7);
}
}
} }

View File

@@ -33,4 +33,6 @@ public class PlayerEntity
private Integer id; private Integer id;
private String name; private String name;
private Instant tracked_since; private Instant tracked_since;
private Instant last_updated;
private Integer rank;
} }

View File

@@ -1,8 +1,8 @@
-- MySQL dump 10.16 Distrib 10.2.9-MariaDB, for Linux (x86_64) -- MySQL dump 10.16 Distrib 10.2.18-MariaDB, for Linux (x86_64)
-- --
-- Host: localhost Database: xptracker -- Host: localhost Database: xptracker
-- ------------------------------------------------------ -- ------------------------------------------------------
-- Server version 10.2.9-MariaDB -- Server version 10.2.18-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
@@ -26,6 +26,8 @@ CREATE TABLE `player` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL, `name` varchar(32) NOT NULL,
`tracked_since` timestamp NOT NULL DEFAULT current_timestamp(), `tracked_since` timestamp NOT NULL DEFAULT current_timestamp(),
`last_updated` timestamp NOT NULL DEFAULT current_timestamp(),
`rank` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
@@ -116,7 +118,7 @@ CREATE TABLE `xp` (
`overall_rank` int(11) NOT NULL, `overall_rank` int(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `player_time` (`player`,`time`), UNIQUE KEY `player_time` (`player`,`time`),
INDEX `idx_time` (`time`), KEY `idx_time` (`time`),
CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`) CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
@@ -130,4 +132,4 @@ CREATE TABLE `xp` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2018-01-20 18:37:09 -- Dump completed on 2019-02-15 21:01:17