我正在尝试构建一个实时测验。用户猜测答案,并向我的 Next.js 端点发布帖子(我将转向 Ably 来处理消息的连接/顺序)。
当用户发布答案时,他们会发送以下数据:
端点然后执行以下操作(PlanetScale DB 事务):
所有这些步骤都在事务中,这可能太多了。如果我每秒 2 个请求对其进行负载测试,则会立即出错。
交易的原因是猜测表中的猜测次数。游戏桌有一个水平场地。它从 1 开始,如果猜测次数 > level * X,则游戏行级别应更新为 2 并向所有连接的浏览器/用户发送实时事件。游戏只能更新一次到下一关。
我知道潜在的瓶颈和安全/速率限制问题,这里没有详细描述。
处理这种情况的最佳方法是什么?实时发布/订阅系统中一个事务中的多个数据库查询?可能我会有 X 个游戏和 Y 个并发用户,并且系统必须处理相当大的负载。 (+- 1000 个并发用户)
优化性能和更好地处理并发性的首要任务是简化数据库事务:尝试最大限度地减少事务内完成的工作量,即只包括关键操作:插入猜测和更新用户截止日期。
非关键操作,例如检查和更新游戏级别,是在事务之外执行的。
对于 PlanetScale,查询见解 应该会有所帮助。
// Inside POST request handler
await db.transaction(async trx => {
// Insert guess
await trx('guessTable').insert({ gameId, answer });
// Update user deadline
// Assuming userDeadline function updates or inserts based on conditions
await updateUserDeadline(trx, userId, newDeadline);
});
对于计算猜测值或更新用户截止日期等操作,请考虑使用批处理或异步更新。例如,您可以根据猜测次数定期更新游戏级别,而不是每次猜测都更新游戏级别。例如,可以在交易后异步调用
checkAndUpdateGameLevel
函数(返回 Promise),从而允许 API 无需等待此操作完成即可响应用户。
// Asynchronously check game level and update if necessary
checkAndUpdateGameLevel(gameId).then(() => {
console.log('Game level checked and updated as needed.');
});
缓存经常读取但不经常更新的数据,例如游戏详细信息和用户权限。这减少了数据库负载并加快了响应时间。确保缓存与数据库的一致性。
// Pseudocode for getting game details with caching
async function getGameDetails(gameId) {
let gameDetails = cache.get(`gameDetails_${gameId}`);
if (!gameDetails) {
gameDetails = await db('gameTable').where({ gameId }).first();
cache.set(`gameDetails_${gameId}`, gameDetails);
}
return gameDetails;
}
另请参阅“使用 PlanetScale Boost 进行 Node.js 查询缓存”以获取更具体的解决方案。
还实施速率限制以防止滥用和排队来管理负载。对用户操作进行排队并异步处理它们可以帮助管理流量峰值。
// Express.js rate limiting example
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
// apply to all requests
app.use(apiLimiter);
这将避免达到 PlanetScale 自己的速率限制(每分钟 600 个请求,如果达到速率限制,则会出现 429 错误响应代码)。
如果可行,将大型事务拆分为专注于关键部分的较小事务。例如,更新游戏级别可以是与插入猜测不同的单独事务。
POST 处理程序中的事务已经专注于关键更新。异步函数
checkAndUpdateGameLevel
展示了如何拆分逻辑以避免长事务。
// Logic to check if game level needs to be updated
async function checkAndUpdateGameLevel(gameId) {
// This might include fetching the current guess count,
// comparing it with the level threshold, and then updating the game level if necessary.
}
最后,确保您的数据库架构针对您正在执行的查询进行了优化。对游戏 ID、用户 ID 和其他频繁查询的字段进行适当的索引可以显着提高性能。
CREATE INDEX idx_guess_game_id ON guessTable(gameId);
CREATE INDEX idx_user_deadline ON userDeadline(userId);
回顾一下:
// Simplified Next.js API handler
export async function postAnswer(req, res) {
const { answer, gameId } = req.body;
// Begin transaction
try {
// Insert guess and update user deadline in transaction
await db.transaction(async trx => {
// Insert guess
await trx('guessTable').insert({ gameId, answer });
// Update user deadline
//
});
// Asynchronously check game level and update if necessary
checkAndUpdateGameLevel(gameId);
res.status(200).json({ message: 'Guess submitted successfully' });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
}
async function checkAndUpdateGameLevel(gameId) {
// Logic to check if game level needs to be updated
// That is done outside of the initial transaction to reduce its duration
}