M.I.T. DEPARTMENT OF EECS


6.033 - Computer System Engineering (Spring 2001) Handout 18 - April 12, 2001

Design Project 1 Comments

Writing Style & Structure

The quality of the writing was generally high, and most of you seem to have a good sense of how to organize a report. To help you avoid some common mistakes in your next report, here are some suggestions:

Design Issues

Most of you reviewed the requirements and prioritized them well. But bear in mind that it rarely makes sense to use terms like "as fast as possible". Surely there is no point in providing temperature updates more often than 10 times a second, e.g.

Protocol Styles

You came up with an imaginative collection of schemes. These included: a broadcast scheme with no routing; a standard routing table constructed with advertisements; source-routing in which the packet carries its route, and each node holds only the route to its friend; and a hybrid, loose-source routing scheme in which nodes on a route contain forwarding links. There were also some rather unusual schemes: a hierarchical address space with an elaborate setup protocol to determine higher-level routers; schemes based on Cartesian coordinates; schemes in which a leader is elected and packets are sent radially into the leader and out (often by forming a tree-like structure); and a particularly remarkable baton-passing algorithm: The holder of the baton floods the network (with back-propagation prevented by a serial number in the packet) and then passes the baton to someone else in a repeated depth-search tree-walk. Each successive flood (in the absence of lost packets) is contained inside the previous flood. The serial number thus can be passed along in the baton, so the cache of previously seen packets needs to remember only that one serial number. This scheme uses memory that grows with log(N)--the size of the serial number.

A few of you designed schemes that worked only on a grid. The FAQ stated clearly that your design, while possibly optimized for a grid, should work in a more general sense. Some of you did manage to leverage this to your advantage, however. Unfortunately, others of you designed schemes that attempted to deduce whether nodes were on an edge or corner by listening for neighbors. Unless this is done with care, lost packets can cause internal nodes to mistakenly identify themselves as edges. Grid-based designs weren't the only ones to dissolve into chaos in the face of lost packets, however!

A lot of you decided to avoid the lost packet issue and many others by simply flooding temperature messages, which, in this constrained system, turned out not to be such a bad idea, as long as you had a way to ensure messages were removed from the network. A bad idea was just to use TTLs, as packets will ping-pong between each pair of beanies, duplicating themselves exponentially. A similarly bad idea was to only prevent cycling of packets: there are an exponential number of non-cyclic paths a packet could take before hitting its destination.

The best way to avoid exponential buildup is to keep a cache of recently seen packets, and refuse to forward those you've already seen. But how big does the cache need to be? Everybody needs to see every packet, so safe guess is N. Some of you tried to claim a smaller one is workable. Whether or not this is so depends on whether you can bound the amount of time (in packets) between the possible arrival of the same packet. This depends on your queue lengths (see below). In addition, transmissions that are heard by only some neighbors can lead to those packets returning much later than usual. Odd topologies (e.g., N beanies in a circle) can make this effect even worse.

The correctness of a caching scheme also depends on what you record about the packet, however. Schemes that forward only one packet per Temp, Src/Dst ID, or both change the semantics of the service provided to the e2e layer. The e2e layer might want to transmit the same temperature twice, and it's not your place to forbid it. A better idea was to select some per-packet nonce, or use timestamps.

Many of you who used the standard routing table noticed that a representation that keys on destination IDs is bad, because it replicates next-hop neighbor IDs many times. Several of you added a table that allowed shorter IDs to be used for neighbors. A better solution is to organize the table around neighbor IDs, especially since computation is free in this system. This needs to be done with care, however, as several such schemes caused problems with duplicate packet detection.

For those of you using a routing table scheme with advertisements, it's better to send the table entry by entry than in one huge message. The link layer does not guarantee delivery, so sending the whole table at once will amplify the consequences of occasional packet loss. (See below for a discussion on queue lengths with large packets).

Source-based routing schemes need some way to prevent congestion from broadcast route requests. It's not good enough to just avoid loops; you need to keep some state at each node to reject recently seen messages. Many of you suggested you would examine ALL the non-cyclic paths, and chose the best one. Let's consider for a moment how many such paths exist.

As most of you pointed out, the nodes with the longest paths between them exist at the opposite corners of an n x n grid, with a shortest path of 2(n-1) hops. Neglecting longer paths (Exercise for the reader: How many non-cyclic paths are there?), there are still a large number of shortest paths. Consider moving from top left to bottom right on a grid. At each of the 2(n-1) hops, you can either move down or right, but you must make an equal number of both. Hence there are (2n-2) choose (n-1) different paths, or (2n-2)!/(n-1)!(n-1)!, which, for n=10, works out to about 48620.

So ONE seemingly harmless path discovery message sent by the corner node will result in its partner receiving almost 50,000 SHORTEST paths, not even considering the longer ones. Clearly this is not going to work. The best way to avoid this is to allow each node to forward only one (or two or three) path discovery messages per source. This prevents the exponential explosion, and, in a grid topology, results in at most 4 (or 8 or 12) path choices at the destination.

Optimizations

Many of you invented clever optimizations. Some of these were: Some of your optimizations were not so good: One optimization that has both pros and cons is using packet length to distinguish packet types. While the unlimited CPU and severely constrained bandwidth may make this a good idea in this particular case, the savings does not, in general, merit the added complexity and maintainability problem; one of you even padded packets with 16 bits to get a different length! Using a uniform packet size and putting dummy values in id fields for some packet types is also a bad idea. It makes the code more complicated, and it consumes valuable bandwidth.

Timing

Finally, many of you struggled with timing, particularly how to move between initialization and normal forwarding modes. Many of you avoided this problem by making the strong assumption that somebody would restart the beanies at the beginning of the hour. While a severe restriction, this was better than those of you that assumed the Beanies had wall-clock time and could do it themselves. (Even if you synced them all at the factory, there WILL be drift). The best schemes adaptively moved into and out of discovery phases, which also nicely handles beanie mobility in many cases. A very clever use of the Beanies timer was to leverage it to remove link-layer queuing from the mix entirely by simply storing all packets in your own memory (the infinitely fast CPU can process incoming packets as they arrive), and proceeding through them in some order, marking them as sent as you dispatched them, and timing the dispatch based on packet length.

Analysis Issues

The most common mistakes in the presentation of the analysis were pulling numbers out of thin air, and presenting formulas full of numbers with minimal explanation. Use symbolic names as much as possible. That doesn't mean you need to solve the resulting equation; some of you showed nice tables of possible parameter settings determined by trial and error.

Most of you could do much better explaining the assumptions that underlie your reasoning. The analysis is very tricky, because of concurrency, queuing, unpredictability, etc. So you have to make some reasonable assumptions.

Many of you had a difficult time reasoning about queue lengths in your network, and blindly assumed they wouldn't be a problem (often without even stating you were ignoring them in your analysis), without any feel for how big queues can get. The most common flaw was to assume that each node could be analyzed in isolation, ignoring the costs of forwarding, for example, for other nodes. Many of you also ignored the trickiest phases or aspects of your scheme (and the ones most likely to cause performance problems!).

Let's consider a flooding protocol for the moment, since almost all of you did some flooding, either to forward packets or to discover routes, and see just how big a queue might become. Consider the portion of a 10x10 grid shown below, and assume every node has one packet to send at time 0. Let's focus on the central node, 0, and consider how many packets it has to forward. It receives 4 packets at each timestep, and sends out only one. So, at the end of k timesteps, the node has 3k packets in its queue.

That's not so bad... but how many are still coming? There are (\sum_{n=1}{k} 4n) - 3k still on their way! After 4 rounds, that's 37 packets that are queued up to be sent out by the central node (either at the central node itself, or on their way in from neighbors). Doh! So we still have to wait for those packets to drain (and make their way through the remainder of the grid) before we can consider the packets sent at time 0 to be fully dispersed.

        4
      4 3 4
    4 3 2 3 4
  4 3 2 1 2 3 4
4 3 2 1 O 1 2 3 4
  4 3 2 1 2 3 4
    4 3 2 3 4
      4 3 4
        4
As you can see, even in this simple case, arguments involving queue length are very subtle and error prone, so it's much better to select parameters that you can argue will cause little or no queuing on average. The easiest way to do the analysis, and to avoid having to reason about congestion, is to calculate the aggregate load of the entire network, and then split it across nodes.

For example, suppose you want to estimate for a broadcast scheme how often to transmit data, and you're using a scheme in which nodes drop packets they've already seen. Then a single packet broadcast results in the transmission of the packet by each node. If all n nodes send a packet, there will be fewer than n^2 packets sent. With a packet size of p and transmission rate of r, these packets can be sent by n nodes in p.n^2 / n.r = p.n/r. Setting r=1000 bits/sec, p=50 bits, n=100, we get 5s. So if we introduce packets no more often than once every 5s, queues will remain short and the network will not get congested.

(You may be concerned that the last packets to leave the longest queue will need additional time to traverse the remainder of the network. This isn't a problem in a grid topology. Since we're flooding, and every packet reaches every node, these packets will have already been seen by adjacent nodes, by virtue of the fact the are the last packets in the longest queue.)

While most of you need only be concerned about queue length to determine packet inter-arrival times, those of you with very long packets (say route discovery packets with a train of 4 byte IDs) also need be concerned about overflowing the queues. In a grid formation, a node can only receive 4 new packets at a time; assuming it sends one at the same time, this results in a queue growth of 3 new packets. With a path length of 18, say (the SHORTEST non-cyclic path for nodes at the corners of a 10x10 grid), your packets may approach 80 bytes. While the link layer had a few hundred bytes of queuing, that only holds about a dozen of these packets. As you can see above, the central node's queue may begin to overflow after 4 rounds, causing packet loss. This is likely not a problem since packets are duplicated, but worth mentioning.

Many people ignored some aspect of their system in the analysis, and attempted to compensate for this omission by adjusting the result with a constant factor. For instance, some people calculated the worst-case message delay ignoring queuing, and then multiplied the resulting delay by k, where k was typically a small integer greater than 1, stating that the effect of queuing couldn't possibly affect the delay by more than a factor k. It should be clear that in the absence of adequate flood control, the effect of queuing could result in virtually unbounded delay.
 
 
Go to 6.033 Home Page Questions or Comments: 6.033-staff@mit.edu