Your task is to write a simple application server that prints a message at a given time in the future. The server has only 1 API: echoAtTime - which receives two parameters, time and message, and writes that message to the server console at the given time.
Since we want the server to be able to withstand restarts it will use Redis to persist the messages and the time they should be sent at. You should also assume that there might be more than one server running behind a load balancer (load balancing implementation itself does not need to be provided as part of the answer)
In case the server was down when a message should have been printed, it should print it out when going back online.
The focus of the exercise is the efficient use of Redis and its data types as well as seeing your code in action. You can implement the exam in any language of your choice (preferably in Typescript/NodeJS).
There's a driver for Redis NodeJS
https://redis.io/topics/data-types
/echoAtTime?time=1575199971&message=Hello You
This GET route receieves two arguments:
- Time: Epoch time in secs - EpochConverter
- Message: A text
This module uses the PubSub
try {
const { time, message } = req.query
result = await pubsub.addTodo(time, message)
}
catch (error) {
...
}
Will yield JSON with status 400/500 in case of error, otherwise will reply:
{
result: "added todo at: 1:41:46 PM [1575200506000]"
}
A module that holds 2 connections to Redis:
- The redis repository
- Subscribing to messages like expiration events
-
Generate a score from given time. For example: time in milliseconds
-
Add the score in todos SortedSet by score
- Save another entry as List to hold all the todos for this score
await this.redis
.multi()
.rpush(`todos:${key}`, `${todo}`)
.zadd('todos', `${key}`, `${key}`)
.bgsave()
.exec()
- Get the minimum score by using range
const rangeOfFirstTodo = await this.redis.zrange('todos', 0, 0)
...
return rangeOfFirstTodo[0]
- Set/Update todo:next separatly as String with Expiration=score (in milliseconds)
await this.redis
.multi()
.set('todo:next', `${minKey}`)
.pexpireat('todo:next', Number(minKey))
.bgsave()
.exec()
- We use the expiration event to pop the first todo score from the sorted set
const [key] = await this.redis.zpopmin('todos')
- Then we update the todo:next to point to the next score and set its expiration accordingly
await this.redis
.multi()
.set('todo:next', `${minKey}`)
.pexpireat('todo:next', Number(minKey))
.bgsave()
.exec()
- Then we enumerate the todos from the todos:score List, print each. Then we delete the list
const todos = await this.redis.lrange(`todos:${key}`, 0, -1)
const dueDate = new Date(parseInt(key)).toLocaleTimeString()
const colorize = overDue ? clc.yellowBright : clc.greenBright
todos.forEach((element, i) => {
console.log(colorize(`[${i + 1}]`, dueDate, element))
})
this.redis.del(`todos:${key}`)
We reach this event after connection is made to redis. Since overdue score no longer has a todo:next, we must query the todos SortedSet for all the past scores until score=currentTime (in milliseconds) We also reach this event after reconnection.
-
Then we show the list of each score that is in the past, then we remove those lists
-
After that, we remove those past scores from the todos SortedSet
-
Finally we update the update or set the next reminder with Expiration time
const currentTime = new Date().getTime()
const pastTodos = await this.redis.zrangebyscore('todos', '0', `${currentTime}`)
if (pastTodos.length > 0) {
for (const key of pastTodos) {
await this.showTodo(key, true)
}
// remove from set
await this.redis.zremrangebyscore('todos', 0, currentTime)
await this.setNextReminder()
}
- Install Redis: brew install redis
Start server with:
- redis-server
Stop server with:
- redis-cli shutdown
https://medium.com/@petehouston/install-and-config-redis-on-mac-os-x-via-homebrew-eb8df9a4f298
- After installing node, run the test script:
chmod +x test.sh
./test.sh
This test does the following:
- Kill all node process
- Stop & Start Redis as a service
- Send t2=currentTime+15s Todo3
- Send t1=currentTime+5s Todo1
- Send t1=currentTime+5s Todo2
- After 5s we expect to see Todo1 and Todo2 (in this order)
- After 7s Redis is going down
- After 20s (At this time, Todo3 is overdue), Redis goes back online
- We expect to print Todo3, when we're back online




