2. •
Cloud hosted SaaS product
•
Provide staff rostering primarily for hospitality companies
•
Focus on rostering staff to ad hoc events, not regular schedules
•
3 founders - I’m the IT guy
4. Solutions
•
Do nothing, let our customers figure it out
•
•
we might not have any customers
Store locks in our mysql database
•
edge cases are ugly
5. Redis - A better way
Redis is an open source, BSD licensed, advanced
key-value store. It is often referred to as a data
structure server since keys can contain strings,
hashes, lists, sets and sorted sets.
6. Redis…
•
Developed by Salvatore Sanfilippo (@antirez)
•
An in-memory data store with optional disk persistence
•
Clusters easily
•
Documentation is amazing!
•
Has myriad uses beyond what I am discussing today
•
Take a look, it will open your eyes to new solutions to age old problems
7. Using Redis
•
predis is the de facto standard library for PHP
•
can be further accelerated with a C extension
•
As a key value store Redis has a very simple
command set
•
We will be focusing on a handful of commands
today
8. How did we do it?
•
Create a set using the event id to form a key name
•
Populate the set with the ids of users who have
opened a read lock
9. Edit action
<?php
class EventController extends Controller {
!
public function editAction($event){
//...
!
//Check for an existing lock and redirect if one exists
$redisKey = 'event_edit_'.$event->getId();
$redis = $this->container->get('snc_redis.default');
if ($redis->exists($redisKey)) {
if (!$redis->sismember($redisKey, $this->getUser()->getId())){
return $this->redirect($this->generateUrl(‘event_edit_conflict’, array('id' => $id)));
}
}
//Create a lock for this user
$redis->sadd($redisKey, $this->getUser()->getId());
$redis->expire($redisKey, 120);
}
//...
10. Releasing a lock
public function updateAction(Request $request, $id)
{
//$businessLogic->doStuff($event)
//etc...
!
//Once the changes have been flushed to the database release the lock
$redis = $this->container->get('snc_redis.default');
$redis->srem('event_edit_'.$event->getId(), $this->getUser()->getId());
!
//...
}
11. Maintaining a lock while editing
Client Side
!
function pollEditLock() {
$.get(
'{{ path(‘event_edit_lock’,
{ 'id': entity.id })}}’,
function(data) {
//Do something
}
);
}
!
setInterval(pollEditLock, 30 * 1000);
!
Server Side
!
<?php
//...
public function editLockAction(Event $event)
{
//...
$redisKey = 'event_edit_'.$event->getId();
$redis = $this->container->get('snc_redis.default');
!
$redis->sadd($redisKey, $this->getUser()->getId());
$redis->expire($redisKey, self::LOCK_TIME);
!
return new JsonResponse(array('ok'));
}
//...
12. Sometimes users want to edit concurrently
public function editConflictAction(Request $request, $id)
{
//...
!
$redisKey = 'event_edit_'.$entity->getId();
$redis = $this->container->get('snc_redis.default');
!
if ($request->query->get('force')) {
$redis->sadd($redisKey, $this->getUser()->getId());
$redis->expire($redisKey, self::LOCK_TIME);
!
return $this->redirect($this->generateUrl('event_edit', array('id' => $id)));
}
$users = array();
$client = $this->get('context.client');
foreach ($redis->smembers($redisKey) as $userId){
$users[] = $em->getRepository(‚ÀòRCRosterBundle:User‚ÀÙ)->find($userId);
}
!
//...
}