Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

Architecture Backend

Alexander Dubrawski edited this page Dec 14, 2020 · 20 revisions

Main Idea

Basic-Architecture

Download Architecture image

The backend consists of multiple components:

  1. Flask
  2. Workload Generator
  3. Database Manager
  4. InfluxDB

All these components are running in separate Processes and are communicating via interprocess communication with each other. The workload generator implements the logic for creating a configurable number of queries per second from a predefined query set. By default, all queries of a workload are chosen with the same probability. Afterward, the workload generator passes the queries to the database objects (managed by the database manager), which maintain query queues (implemented with Python’s multiprocessing library) and handle the actual query sending with psycopg2. We utilize ZeroMQ’s publisher-subscriber pattern for efficient inter-process communication. Flask handles the communication between the backend and frontend. Furthermore, the time-series database InfluxDB is used to store the relevant metrics permanently.

Architecture

Used libraries

Before having a look at the code you should have basic knowledge in python. The backend uses the following important libraries:

Flask

The flask component uses for every namespace (for example /metrics) the following files:

  • controller.py : The controller is responsible for coordinating Flask routes, services, and schemas. On top, the controller is also responsible to provide the documentation for Swagger. To provide the documentation for swagger the controller uses the flask_accepts library in connection with schemas (also used to serialize and deserialize objects) from marshmallow. The controller uses also models (The model is where the entity itself is defined in a Python representation)for type safety. All the requests are delegated to the service.
  • model.py: A model is used for type safety and to represent an entity itself as a Python representation (object). If we for example getting a response from the database manager or influxdb we convert this response, which is often a JSON, to a Python object. These objects are defined by the models. These objects are often composed of other objects.
  • schema.py: A schema is responsible for the serialization/deserialization of a given entity. An entity can be a response from the influxdb or the database manager. This response can then be deserialized (load) into a python entity (model). On the other hand a python entity (model) can be serialized (dump) to a JSON-encoded string. For the deserialization, the post_load decorator is used. For the schemas, we are using the marshmallow library. The schemas are also used by the controller for documentation.
  • service.py : The service is responsible for interacting with the entity. This includes fetching data from the influx or database manager. The data is then if needed deserialized into a Python entity (model) by using the corresponding schemas.

The main libraries used are:

Manager and database objects

The Database manager manages the database objects. Every database object represents a Hyrise instance and only this object communicates with it. If the manager gets a request from the Flask application, for example, to activate a plugin, it delegates this request to the right database object. The database object itself then sends a request to the Hyrise database.

Worker logic

The workers are the central part to run a workload on the Hyrise databases. There are two kinds of worker:

  • queue_worker
  • task_worker

Every database object has one worker pool consisting of one queue worker and n task workers. Every worker runs in its own Process. Since the python interpreter can only execute one Thread per time, it relies on Processes for parallel code execution (https://stackoverflow.com/questions/3044580/multiprocessing-vs-threading-python). The queue worker subscribes to the Zeromq socket of the workload generator (publisher) (https://zguide.zeromq.org/docs/chapter2/#Multipart-Messages). If it gets some tasks (list of SQL queries) from the publisher it places them in a thread save multiprocessing queue (https://docs.python.org/3.8/library/multiprocessing.html). The task workers are then extracting a query from the multiprocessing queue and executing it. The task worker is then saving the latency of the queries every second to the influx DB.

Connection factories

For all connections to the influx DB and the Hyries database, the cockpit is using self-defined cursor objects hyrisecockpit/database_manager/cursor.py. These are just wrappers around psycopg2 and InfluxDBClient objects. cursor.py also provides factories for these cursors. The database object database.py initializes a ConnectionFactory (for the Hyrise database) and a StorageConnectionFactory for the influx DB. All jobs can then use these factories to create a cursor when they need one. All connection information is saved in the factory object from the database object (database.py).

Background Jobs

Every database object needs to communicate with its Hyrise instance. To get meta information and to change the state of the Hyrise (for example load tables). The communication logic is defined by jobs (hyrisecockpit/database_manager/job/). We have three kinds of job handlers that are executing these jobs:

  • jobs that need to be continuously in the background (for example: get the system information) are executed by the continuously_job_handler
  • jobs that will be done asynchronously (for example: loading the tables) are handled by the asynchronously_job_handler
  • jobs that are done synchronously (for example: sending a SQL query over the SQL endpoint) are handled by the synchronous_job_handler

Interprocess communication

The Flask app is communicating with the database manager and the workload generator via a simple REQ/REP pattern (https://zguide.zeromq.org/docs/chapter1/#Ask-and-Ye-Shall-Receive). Since the Flask app runs multi-threaded, every thread needs to create its own socket connection. For that, it can use the connection manager (hyrisecockpit/api/connection_manager.py). The flask app only fulfills the role of the client in the communication. The workload generator and the database manager are fulfilling the role of the server. For that, they are instantiating a server object (hyrisecockpit/server.py). It is just a wrapper around a zeromq server. The workload generator and database manager are initializing the server with a dictionary that maps to the handle function defined in the workload generator (hyrisecockpit/workload_generator/generator.py) and on for the database manager in (hyrisecockpit/database_manager/manager.py). The request for the server needs to map a key in the dictionary. The value of this key is a reference to a function that will handle this request.

The workload generator and the workers are communicating with the PUB/SUB pattern (https://zguide.zeromq.org/docs/chapter2/#Multipart-Messages). For that, the workload generator initializes a publisher zeromq socket (hyrisecockpit/workload_generator/generator.py). The worker then simply subscribes to this socket.

Crossplatform support

To support the Cockpit on MacOSX, the use of an adjusted multiprocessing queue (https://docs.python.org/3.8/library/multiprocessing.html) is needed. On Mac, the queue does not support the qzise property. For that, a wrapper is implemented in hyrisecockpit/cross_platform_support/multiprocessing_support.py. All modules that need a multiprocess queue need to import it from this module.

Besides the multiprocess queue, on Mac the default implementation of a MagicMock is not threadsafe. To fix this issue a wrapper around the MagicMock solves this problem. In all tests that are testing the multithreaded behavior of the cockpit, an import of hyrisecockpit/cross_platform_support/testing_support.py is needed.

Drivers

You can find all the needed information in the Implementing a Driver section https://github.com/hyrise/Cockpit/wiki/Implementing-a-Driver .

Influx DB

TODO

(time range (flask)) (continuous queries)