This example demonstrates the use of Nextmv's discrete event simulator, Dash, for customers using Nextmv Enterprise.
In this example, we detail a single-server queue simulation.
Problem Definition
For a single-server queue problem, you have a line of customers waiting to be served and a single server to handle their requests. The server handles one request at a time in the order they arrive (e.g., a line at the bank or grocery store).
The key performance indicator is "wait time", defined as the time from entering the queue to the time entering service (or how long customers have to wait in line before being served).
Input Data
Assume you have historical information about customers, including their unique customer numbers, the number of minutes after opening our queue for business that they arrive, and the number of minutes it takes to service them.
Your input data can then be defined in JSON as a series of customers with the historical information detailed above as attributes.
Customer 0 arrives exactly at the start of the operating period and takes 3 minutes to service. Customers 1 and 2 arrive a minute after customer 0, and will therefore have to wait in line.
Customer 0's wait time should be 0, since they are serviced immediately upon entering the queue. Customer 1 has to wait 2 minutes, while customer 2 will have to wait 12. The simulation takes care of all the bookkeeping required to compute these values.
Customers
Dash operates directly on JSON data using Go structures. Next, define a customer.go
file containing a customer
type. It has the same fields as the input JSON. The customer type also has access to the event and measure ledgers of the simulation, which it uses to publish and subscribe to events, and to record measurements of wait time. Finally, the customer has pointer fields called arrivalTime
and serviceTime
for maintaining its internal state.
Customer Types
Actors in Dash communicate through events. These events are published and subscribed to using an event ledger. An event can be anything that can be stored and converted to JSON.
There are two events that pertain to customers: times they arrive and enter the queue and times they start being served. We call these events arrival
and service
. For expediency the customer
type is simply aliased, however these could be defined as custom types instead.
Next, we define a measure type for recording the amount of time a customer waits in the queue. Measures are published to a ledger and follow the same rules as events. waitSeconds
is defined as a simple integer to track how long customers wait (not who has to wait).
Customer Run Method
Dash works by maintaining a set of active actors in its simulation. When constructing a new simulation, actors are added to it with an initial time to "run" them. Dash adds them to its pool of actors, and calls their Run
methods at the specified times.
All actors must implement Run
. This method takes in the current time, updates the actor's state, and returns the next time to run them along with a boolean indicator of whether not there is anything left for them to do.
The example Run
method is shown below. It has three sections. In the first, the customer tests to see if they have entered service. If so, they publish a measurement of their wait time in the queue, and tell Dash that they don't have anything left to do.
The second section contains logic for a customer arrival. If a customer hasn't recorded its arrival time yet, it does so and publishes an arrival event. Finally, in the third section the customer waits in the queue.
Customer Events & Measures
Since the example customer type publishes measurements of its wait time, it must implement Dash's Measure
interface. This means defining a method named MeasureTo
so Dash can tell it where to publish measurements.
Similarly, the customer communicates its arrivals to other actors through events. To do so, it must implement the Publisher
interface. This requires a PublishTo
method.
The example customer publishes arrival events and responds to service events. In order to update its state based on events published by other actors in the simulation, the customer implements the Subscriber
interface through SubscribeTo
. Note that SubscribeTo
receives a ledger.Subscriber
type, which lets us associate a handler with an event type.
Any number of handlers can be created for a subscriber. These handlers are updated before each call to the actor's Run
method. This means that when Dash selects a customer actor to run, it first processes all events published to the event ledger since the last time it ran. Thus our customer's state is kept up to date.
Server
The example server implementation has many of the same components as the customer. The server does not have any data in the input JSON, so define the server type in server.go
as a queue of arrival events to process and an event ledger to publish to.
The server's Run
method is simple. If the server has a nonempty queue of customer arrivals to serve, it starts the first one and publishes their service time. The server is then busy until they are done servicing that customer. Otherwise, the server waits for something to do.
A server publishes service events just like a customer publishes arrival events.
Finally, a server subscribes to arrival events. Whenever it encounters one, it adds that arrival to its queue.
Runner
A Dash runner is needed to read input data into the queue simulation, run that simulation, and aggregate its output. The CLI runner takes a handler with a variable to unmarshal the input into and options parsed from the environment and command line flags, and returns a simulator.
Next, build the simulation into an atomic binary and run it on the example input.
Note that the time
is the amount of simulated time to run for and limits
is the actual run time limits. Use time
to set how much simulated time to run for and limits
to set time limits. For example, setting a time
of 10 hours will run 10 hours of simulated time, but pairing that with limits
of 1 minute will ensure that the simulation stops after 1 real minute of time, regardless of whether those 10 simulated hours have completed. In our model, there is always a server actor lurking about, so we have to tell Dash when to quit.
or, with command-line file input/output flags
By default, this doesn't give us much information beyond input options and time statistics.
We can tell Dash to include its event log in the output. This adds an events
field to the JSON.
or
In this example, customers publish arrival events, which the server subscribes to. The server publishes service events, which the customers subscribe to. The customers measure their time spent in the queue and publish those measurements to the measure ledger. This requires that the customers implement MeasureTo
.
Requesting measurements from Dash is similar to requesting events.
or alternatively
In this example, the waitSeconds
measurement type is simply an int
. If we want more information we can make it a struct or provide a MarshalJSON
method, and Dash will automatically incorporate those details into its output.
Randomness
Dash also has the ability to sample randomly from a distribution to represent behaviors. Check out the Random Queue example for more details.