[erlang-questions] Priority Queue

Jay Nelson jay@REDACTED
Sat Jul 7 18:42:05 CEST 2007


As Ulf pointed out I wrote some charts about priority messages for  
the 2006 EUC.  While it covers the topic, some of the notes now seem  
a little cryptic because they are in briefing format rather than as a  
descriptive document.  More embarrassingly the references to N!  
complexity should really be 2(N+1) (instead of multiplying the  
factors N, N-1, N-2 ... 1 they should be added to get the complexity  
since the position of messages in the queue is never modified).  So  
in the end it turns out to be linear complexity, but with large  
queues the problem is big enough to cause serious delays as others  
pointed out earlier.

The discussions about extending the language were concluded with no  
change because it really is an internal optimization issue.  The  
current mechanism in the messaging system is simple and reliable, but  
could be improved to handle selective receive better -- although all  
the suggestions only sped up simple cases and made no difference in a  
complicated set of multiple selective receive statements.

The main discussion in the slides was geared at beginner to  
intermediate erlang programmers, but was also intended for experts to  
step back and remember the fundamentals.  It is easy to get overly  
sophisticated in messaging situations when adhering to a few simple  
principles can make a complicated solution simpler.  And simplicity  
can often lead to the discovery of more efficient or better  
performing solutions.

Selective receive is indeed the most powerful approach, however, it  
also is easier to make a mistake with by leaving messages in the  
queue that choke the system over time, or by creating too many scans  
across the message queue.  When you find yourself confronted with  
multiple receive statements spread across separate functions and a  
poorly performing system, consider revisiting the problem and adding  
processes to filter the queue more finely.  Especially now that we  
have SMP support.

Eranga wrote:

 > A simple solution would be to extend Erlang process/message  
passing model to
 > enable to originate priority and normal type messages. When it's a  
priority
 > message, the message is placed at the beginning (head) of the  
queue. When
 > it's a normal message, it's placed at the end of the queue.
 >
 > Taking gen_server as an exmaple there're 2 functions,
 >	gen_server:pri_call/2/3
 >	gen_server:pri_cast/2
 > when run, it places the new message at the beginning of the  
message queue to
 > the gen_server process?

Philosophically, I don't believe it is correct for the sender of a  
message to determine the priority.  If I call Bill Gates and leave an  
urgent message on his answering machine, I may not be aware that he  
is currently in negotiations with Steve Jobs about a new product (my  
standing falls below Jobs but above Larry Ellison) and thus should  
not be urgent.  The point is that the sender of the message has no  
information other than that it would like a response quickly.  Only  
the aggregator or receiver has a global view (and even then it might  
not be inclusive, as new processes are added later) and can determine  
the proper priority.  It would be even worse to rearrange the message  
order externally rather than tagging the messages, but even tags  
require the entire system to have global knowledge of all  
priorities.  Any adjustments would ripple through every process that  
can send a message to the priority queue process.

A better design encapsulates the knowledge in one place so that it  
can be tweaked and tuned to improve performance without modifying the  
rest of the system.  Create a router process and send the  
undifferentiated stream of messages to it.  The router can be a pure  
erlang implementation of the mailbox semantics you would like to  
see.  It should immediately unload every message to an internal queue  
(with only two priorities you can use two queue instances, with more  
you may need the ets or gb_trees approach).  The messages can then be  
forwarded from the router to different processes.  The problem is  
that you need to pause all processes except the highest one that  
currently has messages in the queue, thus the need for control  
messages between router and workers and the desire to keep the worker  
queues empty so that the signals can be recognized without much  
overhead.

The big advantage with a router process is that you don't have to  
allow high process to hog all the CPU.  If you recognize the  
situation where high outnumbers low by a lot, you can periodically  
cede control to the low priority processes.  You can only determine  
if this is necessary in a running system by measurement and tuning.

So here is the real tradeoff when trying to do priority messaging:

1) The cost of selective receive could be high when a burst of  
messages arrives
2) Splitting to a router gives more control, but more latency in the  
message delivery and more overall resources used by the erlang node

Use technique #1 and model your message traffic profile.  If it is a  
problem your queue will get large and stay that way for a minute or  
more, and you will start to see large latency in the handling of  
messages.  Switch to method #2 and see if you can reduce overall  
packet latency by managing the routing yourself.  The key to getting  
this right will be the method used for controlling which process runs  
and how the traffic is doled out.  If you have a problem in case #1,  
you should be able to improve latency using case #2 even though there  
is an intermediary for messages -- the problem will come if the  
router collects messages faster than it can get them serviced.

jay




More information about the erlang-questions mailing list