Introducing Bingo Bridge for Gyroscope December 17, 2021

View all articles from Gyroscope Development Blog

Bingo Bridge, or "Bingo" for short, is a web execution flow that runs binary code that's compiled from Go alongside PHP. Starting 18.8, Gyroscope uses Bingo Bridge to seamlessly branch selective components to the Go version of Gyroscope.

A Short Version

Before we dive into the technical aspects of Go, PHP, the bridge and Gyroscope use cases, let's take a quick look at the impact of new feature.

Memory: The same server can host up to 100 times the traffic, with varying mileage.

CPU: High dimensional faceted navigation dashboards typically run 3 times faster, depending on the CPU cores and workload.

Network: Communication to backend servers such as MySQL and ClickHouse is much more efficient thanks to resource reuse and binary protocol. This is an average 30% improvement. The Go version of WSS is also more responsive due to its synchronized, yet parallel structure.

Among all the benefits, the most captivating aspect is memory efficiency. How does it Feel to use 1/100th of the memory? To put things in perspective, we built two paper cubes - one represents the volume of 20 kilobytes, and the other 2048 kilobytes - that's roughly 100 fold. We'll explain the significance of these numbers later.

Why Go

Go is a general purpose, compiled language with many unique features that we can leverage to enhance web application performance and efficiency. Just to be clear: our adoption of Go does not mean a departure from PHP. With a "bridge" between PHP and Go, we get to have the best of both worlds. It is also important to point out that language alone does not make a better platform or application. In fact, if not careful, a program written in Go can be grossly inefficient and slow. The PHP version of Gyroscope is already more efficient than many Go-based web frameworks as is. We use Go strategically to give the PHP codebase a further boost.

The execution path of a Go web app is very different from that of PHP. Instead of sitting behind a web server such as Apache or Nginx and handling stateless requests, a Go app typically listens on a port as a standalone web server. In production, this server is placed behind a frontend webserver, so that static assets like images and CSS stylesheets are offloaded. The Go server stays in memory; this means that a new HTTP request made to Go does not have to translate to a new MySQL request. The communication between Go and its backend servers stays active.

The following diagram shows the already brief double-digit millisecond request in PHP being reduced to a single digit in Go.

Although our goal is not to pinch every millisecond, especially considering that the bottleneck of a web application is often elsewhere, the no-waste response in Go sheds some light in Go's overall economy.

Being binary machine code, Go is also memory efficient. A PHP object, in comparison, takes up much more space. Despite the drastic improvements in memory efficiency in newer versions of PHP, the object footprint is still rather large due to the loosely typed nature of PHP. A very telling metric is the minimum memory consumption per web request. In PHP, on a 64-bit machine with opcache enabled, a minimum of 2 megabytes, or 2048 kilobytes is consumed. In the Go version of Gyroscope, including the framework overhead, each request consumes 15 kilobytes of heap memory - this is less than 1/100th of PHP memory use.

In case the previous cube photo didn't drive it home, here is another look at the cubes, in a frame that represents 1G of RAM. How many concurrent users a server can handle roughly relates to the number of cubes that can fit in the frame.

A Gyroscope app contains requests of all sizes, many of which are extremely brief "micro requests". For example, a logpump, an authpump or even a list view can consume very little memory. When the majority of these "trivial" requests are handled by Go, more memory becomes usable for more important things.

Another unique language feature of Go is parallel processing. Using Go routines and channels, a process can gracefully split into asynchronous routines. The results of these routines can then be combined either in an ordered fashion, or as an ongoing symphony of events. Traditionally, synchronizing threads can be tedious and error-prone, even if the language supports multi-threading. Without breaking into code examples, let's just say, Go's handling of such matters is Graceful at least.

At this point, it's important to mention that the choice of Go for the binary bridge is not a trivial decision. We did not start with "what can we do with Go". Rather, the properties of Go caught our attention first. Further investigation of the language confirmed its suitability for many optimization and IP protection goals. Only then did we redesign part of our web architecture to fully embrace the strengths of Go.

Gyrosgo - the Go version of Gyroscope

The Go version of Gyroscope "Gyrosgo" is a "close enough" clone of its PHP counterpart. Gyrosgo has the same component layout. Developers who are familiar with myservices, settings, auth and language packs should find parallels in Go. The source IP digest, user verification and other "auth clocks" are synchronized so that it's possible to send one request to PHP and another to Go, all transparently from the end user.

Gyrosgo is on a private repository as a commercially-licensed product. It is not open source or in the public domain. The switching/bridging code is open sourced in PHP. The Gyrosgo repository contains a main Gyroscope app, a web socket server (WSS) that bundles GS Chat, and a set of utilities written in Go.

Why not Node?

Well, a "binode bridge" just doesn't sound as fun as a "bingo bridge" does it? Jokes aside, node.js really is unsuitable for what we use Go for despite some similarities. Maybe now is time to clarify some common misconceptions.

- "a Node server can sit behind a frontend server just like a Go server" - Yes, but not exactly. A Go binary that binds and listens to a port as a standalone web server is a standalone, portable executable. Once built, it will run regardless of module version changes. A "node server" is a JavaScript running in a node.js runtime. A node application as as (financially) independent as inviting your friends to a house party while buying all the booze with daddy's credit card.

- "a Node program runs close to machine code performance" - the keyword is "close". node.js is not binary. The V8 engine is a just in time (JIT) compiler runs JavaScript at boosted speed. Both V8 and the dynamically typed JavaScript language pose many restrictions on machine code generation. The raw performance of node.js is nowhere near a Go binary.

- "node.js can run parallel processes" - Absolutely not. Node uses many internal threads to function. But for the JavaScript execution it is single threaded. The callbacks, promises and async/await syntax gives the illusion of parallel execution, but there is a difference between non-blocking and multi-threading.

Having mentioned all the inadequacies of node.js for our use cases, the bridging mechanism for Go can be applied to other sub-server processes such as a Node server.

Setting up Bingo Bridge

As mentioned earlier, the Go version of Gyroscope responds to certain web requests in an identical way as the PHP version. The end user won't tell the difference. The PHP code has a few places to specify whether a request should route to Go or PHP. But first, a bridge between the two Gyroscope worlds must be established.

The vitals of each Gyroscope must be in sync. Timezone settings, gskey and the session salt are the main contributors to this synchronicity. The movie Avatar hinges on the concept of a Mind/Psionic Link between a human operator and an artificial body:

The most telltale sign of a successful bridge is when logpump.php and logpump.gsb return the same token.

Another diagnostic tool is sysinfo.gsb. It displays the digested client IP addresses in comparison to the ones that are displayed in sysinfo.php. The sysinfo.gsb page also displays the cumulative heap memory usage. This is useful for measuring the memory uptake of a single web request.

In settings.php, set the $binpage variable to "myservices.gsb". Then, when calling tab functions, pass bingo:1 in the opts object parameter. Gyroscope will remember whether a list view or a tab is in Bingo mode.

Manual calls to document.appsettings.codepage via ajxpgn should be replaced with document.appsettings.binpage.

The reports table now contains a "bingo" column. When set to 1, the rpt request is routed to Go. The PHP switch myservices.php does not even need an entry for Go requests.

The nav_loadcharts function for faceted dashboards also contains a "bingo" flag. Make sure to enable this in reports.js for selected reports.

Faceted Navigation and ClickHouse

A faceted navigation dashboard summarizes the data distribution in many categories, or "dimensions". For example, in a collection of shoes, we can see the breakdown in sizes, brands, styles and colors. Without Go's parallel processing, we had to either run all the dimension queries in sequence, or use GSX (curl_multi) to awkwardly split and harvest the summaries, or split at the MySQL level (mysql_multi), which is also cumbersome and unreliable.

The performance of faceted navigation is further boosted by ClickHouse - a vertical-store, horizontally scaled database that's extremely fast with group-bys. We already adopted ClickHouse for the PHP codebase. However, the PHP connector uses HTTP requests whereas the Go version uses the binary MySQL protocol.

Combining persistent connection, binary protocol, ClickHouse performance and Go's parallel processing, it's easy to imagine that a faceted dashboard using Bingo Bridge can be insanely fast.

The above two diagrams simulates the millisecond delay. The first one is more perceivable. The blink in the second diagram is much shorter. In fact, your eyes blink more often than you realize. Depending on the "frequency" of your eyes, you may not always see the blink in the second chart. This should give you a sense of the extreme speed we're talking about.

Web Sockets

The next area of improvement is the Web Socket Server (WSS). Technically this is not a Bingo Bridge. Rather, it is a drop-in replacement for the PHP version of the server.

The Go WSS supports more registries so that a broadcast can target an individual connection, or all the devices under the same logical user, or all the users in the same GS container, and the list goes on. Although the same design could be done in PHP, the registry overhead would be much bigger because of PHP's large objects.

WSS in Go is also more responsive because of the independent event loops. Connecting, disconnecting, keep-alive, message fetching, status broadcasts are all independent Goroutines. When a web socket client opens a connection, the client is instantly registered. Disconnection is also detected without any delays. The PHP version of WSS, in contrast, connects with a noticeable "hesitation". This is because the messaging pull, which takes a 30ms break in each cycle, shares the same event loop as the connection book keeper.

Nginx Setup

single instance:

location ~ \.gsb$ {

    proxy_pass http://127.0.0.1:9990;
    proxy_set_header X-Real-IP $remote_addr;
}

multiple instances:

location ~/subfolder1(.*)\.gsb$ {
    proxy_pass http://127.0.0.1:9990;
    proxy_set_header X-Real-IP $remote_addr;
}

location ~/subfolder2(.*)\.gsb$ {
    proxy_pass http://127.0.0.1:9992;
    proxy_set_header X-Real-IP $remote_addr;
}

Apache Setup

Enable mod_proxy and mod_proxy_http

<VirtualHost _default_:80>
    ProxyPassMatch ^/(\S+\.gsb)$ http://127.0.0.1:9990/$1
</VirtualHost>

IIS Setup

Install the url-rewrite extension.

Modify web.config:

<rewrite>
  <rules>
    <rule name="ReverseProxyInboundRule1" stopProcessing="true">
      <match url="(\S*.gsb)" />
      <action type="Rewrite" url="http://127.0.0.1:9990/{R:1}" />
    </rule>
  </rules>
</rewrite>

Our Services

Targeted Crawlers

Crawlers for content extraction, restoration and competitive intelligence gathering.

Learn More

Gyroscope™ ERP Solutions

Fully integrated enterprise solutions for rapid and steady growth.

Learn More

E-Commerce

Self-updating websites with product catalog and payment processing.

Learn More