Challenges / The concert that oversold
The concert that oversold
Box Office counts the tickets it has left. Keep the concert from selling more than it has when Box Office scales out.
The setup
Box Office sells the concert's 500 tickets. Five seconds in, it scales out to three instances to keep up with the buyers.
Sell every ticket exactly once, however many instances are running. Tickets is a table every instance can reach.
The run
Each goal is judged over its own stretch of the run rather than the whole of it.
- 5 s · Box Office scales out to three instances
- Every buyer reaches Box Office through Ticket BalancerAt the start
- Nobody is turned away while tickets are left0–10 s
- Exactly 500 tickets are sold0 s–end
- Every buyer is answered, with a ticket or a sold out0 s–end
The code
Ordinary Node against the real AWS SDK, running as real processes in the tab. You can edit any of it once the challenge is open.
import express from 'express';
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
// Glass Garden gives every instance its own PORT, because they all share one machine here.
// In a real deployment they would each be on their own machine, and could all listen on the same port.
const port = Number(process.env.PORT);
if (!port) {
throw new Error('PORT is not set');
}
// Set by the table this node is connected to. The endpoint and the credentials the SDK
// needs to reach the local AWS region arrive in the environment too, which is why the
// client below takes no arguments at all. You can see every one of them in the Config tab
const tableName = process.env.DYNAMODB_TABLE;
if (!tableName) {
throw new Error('DYNAMODB_TABLE is not set. Connect this instance group to one table node.');
}
const dynamodb = new DynamoDBClient({});
// The tickets this process has left to sell. A variable lives in the memory of the process
// that declared it, and starts again at 500 whenever a process starts
let remaining = 500;
// Embedded Metric Format: the shape CloudWatch extracts metrics from a log line, and how
// anything running here puts a number on its own Metrics tab
function putMetric(name, value, unit) {
console.log(
JSON.stringify({
_aws: {
Timestamp: Date.now(),
CloudWatchMetrics: [
{ Namespace: 'glass-garden', Dimensions: [[]], Metrics: [{ Name: name, Unit: unit }] }
]
},
[name]: value
})
);
}
// Once at startup, so the name is in the Metrics tab before anybody buys a ticket
putMetric('tickets sold', 0, 'Count');
const app = express();
app.use(express.json());
// The load balancer asks for this before it sends any buyers here, the way an ALB checks a
// target group. An instance that stops answering it is taken out of the rotation
app.get('/', (req, res) => {
res.type('text/plain').send('ok\n');
});
app.post('/tickets', async (req, res) => {
const { buyer = 'nobody' } = req.body;
// Sold out is an answer, not a failure, so it is a 409 rather than an error
if (remaining === 0) {
res.status(409).json({ status: 'sold out' });
return;
}
remaining -= 1;
// Every ticket sold is recorded, keyed by who bought it
await dynamodb.send(
new PutItemCommand({
TableName: tableName,
Item: { id: { S: `ticket-${buyer}` }, buyer: { S: buyer } }
})
);
putMetric('tickets sold', 1, 'Count');
res.status(201).json({ status: 'sold' });
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});More challenges
Your first challenge
Nothing is sending the app traffic, and its code fails on every request. Wire it up, fix one line, and watch a run score itself.
Instance Group
Signups grind to a halt
The signup API hashes every password and stores it while the caller waits. Keep signups answering when the traffic climbs.
HTTP Load Balancer, Instance Group, Table (DynamoDB)
Usually solved with: Queue (SQS), Function (Lambda)