Bitmap Router
  • 24 Jul 2024
  • 6 Minutes to read
  • Contributors
  • Dark
    Light
  • PDF

Bitmap Router

  • Dark
    Light
  • PDF

Article summary

Bitmap Router Nodes are Nodes that allow you to make decisions about messages and to do so in constant time (i.e. - O(1)) in relation to the number of routes attached to the Node.

With a Bitmap Router Node, you can:

  • Filter messages by providing no route for the message.
  • Route messages to any combination of destinations based upon any number of truthy characteristics of a message in any combination.
  • Route all messages to a default destination.

Bitmap Router Nodes accomplish this by creating a bitmap (i.e. - an array of bits, represented as an integer) which contains a list of truthy attributes about the contents of each message. This message bitmap is then compared to a list of route bitmaps using the expression:

message_bitmap & route_bitmap == route_bitmap

When this expression is True for a particular route, the message is routed along that route.

Why use bitmap routing

Bitmap routing comes primarily from the world of network content-based packet routing and is one of, if not the, fastest way to accomplish complex content-based routing. This method is able to do this because, unlike rule content-based routing, bitmap routing is a constant time algorithm in relation to the number of routes.

Normal rule routing creates a rule per route, and the message is passed through each route's rule to determine if the message shoudl follow that route. This works great when there are only a few routes to examine, but begins to become performance inhibitive when the route count rises.

For example, consider a Node that is routing to 3 routes, and each evaluation of the message's contents takes 30ms. This Node would be able to route each message that it receives in 90ms, which is not bad.

However, as your processing network grows, this Node acquires 30 routes. Now the processing of each message takes 900ms, and the routing Node has become a performance bottleneck in your procecssing flow.

In the same scenario, the Bitmap Router Node would process a message to 3 routes in 30ms (the message is evaluated once) and to 30 routes in 30ms (the message is evaluated once)!

In summary, while bitmap routing is somewhat more complex than rule-based routing, it is orders of magnitude faster and far more scalable.

Bitmapper Function

To effect the message bitmapping, Bitmap Router Nodes use a Python function that is provided by you. You may provide this Python function either directly in the Bitmap Router Node (an inline function) or by specifying the name of a Bitmapper Function in your Tenant's Function Library.

Bitmapper functions must conform to the following template:

def bitmapper(*, context, message, source, **kwargs):

    # A message bitmap of 0 will only match the default route!
    bitmap = 0x0

    # TODO - Perform conditional bitmapping of the message here.
    # The returned bitmap will be compared against the routes in the
    # route table for route matching. This will be done by
    # (message_bitmap & route) == route. If no routes match, the
    # message will be filtered.

    return bitmap

NOTE - you must not have any Python code outside of the single function def statement!!

A message bitmap of 0x0 will only match the default route (see below). If you do not have a default route, these messages will be filtered. A message bitmap of all 1's (e.g. - 0xFFFFFFFF) will match every route.

Arguments

The arguments for your bitmapper functions are all keyword arguments. an explanation of the arguments are below:

ArgumentTypeDescription
contextobjectThe Context object providing execution environment information and helper objects and methods.
messagestrThe message itself as a string. You will have to decode this string if it actually represents a complex object (e.g. - json.loads(message))
sourcestrThe name of the Node that sent the message to you Bitmap Router Node.
kwargsdictThis is present specifically to future-proof your function. If, in the future, EchoStream Bitmap Router Nodes pass additional arguments to your bitmapper function, those additional arguments will not break the call to your function.

Return

Your bitmapper function must return an int that represents the bitmap of truthy values representative of the content of message.

Each bit in the returned bitmap can represent anything that you wish. Remember, your routes will have a bitmap that will be compared against the returned bitmap to determine routing.

For example, let's say that we're analyzing the content of a JSON message that has the following structure:

{
    "first": "abby",
    "last": "bottoms",
    "age": 20,
    "income": 100000
}

And the following bitmapper function:

def bitmapper(*, context, message, source, **kwargs):
    
    import json
    if not isinstance(message, dict):
        message = json.loads(message)

    bitmap = 0x0

    # eval first
    if message["first"].startswith("a"):
        bitmap |= 0x1
    elif message["first"].startswith("b"):
        bitmap |= 0x2
    
    # eval last
    if message["last"].startswith("a"):
        bitmap |= 0x4
    elif message["first"].startswith("b"):
        bitmap |= 0x8
        
    # eval age
    if message["age"] <= 20:
        bitmap |= 0x10
    elif message["age"] <= 30:
        bitmap |= 0x20
    else:
        bitmap |= 0x40
        
    # eval income
    if message["income"] <= 50000:
        bitmap |= 0x80
    elif message["income"] <= 100000:
        bitmap |= 0x100
    else:
        bitmap |= 0x200

    return bitmap

For the smaple message above, this would result in a message bitmap of:

bitmap |= 0x1 | 0x8 | 0x10 | 0x100
hex(bitmap) # prints '0x119'
bin(bitmap)[2:] # prints '100011001'

If we then wanted to route all messages where the age was <= 20 and the income was <= 100,000, we would construct the route bitmap:

route |= 0x10 | 0x100
hex(route) # prints '0x110'
bin(route)[2:] # prints '100010000'

We could also create other routes that are any combination of the truthy values that our bitmapper function evaluated.

Requirements

You can use any package available on PyPI that supports Python3.12 or higher in your Bitmapper function.

Simply add the requirement to the requirements of your Bitmap Router Node and it will be included in your Node. Thisis done using pip requirement specifiers.

For example, to include the most popular PostgreSQL database adapter for Python (psycopyg), you would put the following requirement in your Node's requirements:

  • For the latest release:
psycopg2
  • Pinning the release:
psycopg2 == 2.9.3
  • Ensuring a baseline release
psycopg2 >= 2.9.2

Then in your Bitmapper function, you can use psycopg2 by importing it, as follows:

def bitmapper(*, context, message, source, **kwargs):
    
    # Make SURE that you import INSIDE of the function!!
    import psycopg2

    bitmap = 0x0

    # TODO - Perform conditional bitmapping of the message here.
    # The returned bitmap will be compared against the routes in the
    # route table for route matching. This will be done by
    # (message_bitmap & route) == route. If no routes match, the
    # message will be filtered.

    return bitmap

NOTE - All imports must occur inside your function definition to be recognized and executed by EchoStream!!

Route Table

Once you have bitmapped the message's content you have to define the routes that message will take. This is done by defining the Route Table.

The Route Table (routeTable in the API) is a dictionary (aka - map) with the route bitmap as the key and a list of target Node names (the other side of the sending Edges) as the value.

The route bitmap is a string-formatted hexidecimal value (e.g. - '0x110') that represents the truthy values that must be present in the message bitmap to have the message follow this route.

The list of target Node names dictates where the message will be sent. This list must have at least 1 item; there is no maximum.

For example:

{
    "0x0": ["defaultTarget"],
    "0x2": ["targetA", "targetB", "targetC"],
    "0x21": ["targetC", "targetD"]
}

The following expression is used to evaluate the routes to send the message on:

message_bitmap & route_bitmap == route_bitmap

A message bitmap of 0x3 using this Route Table would result in a route to all listed targets except targetD, while a message bitmap of 0x25 would only route to defaultTarget, targetC and targetD.

Targets that are not listed in the Route Table will never have a message sent to them. Targets that have 0x0 as the route bitmap will receive every message processed by the Bitmap Router Node (since anything and 0x0 equals 0x0).

Duplicate targets

You can place a target name in as many routes as you wish. If a message evaluates to more than one route, and your target appears in more than one of those routes, the Bitmap Router Node will only send that message once to that target.


What's Next