Un serveur OLAP en 15mn

Cet article donne les informations nécessaires pour installer Cubes, un serveur HTTP OLAP écrit en Python. Comme je ne vais l’installer que pour jouer avec, je vais utiliser Docker pour créer un container avec une base de données PostgreSQL et un autre pour installer le serveur.

Préparation de la base de données

Cette étape est assez simple, je ne vais pas la détailler plus que nécessaire. Je vais simplement créer un container basé sur l’image officielle PostgreSQL, m’y connecter et insérer quelques données de tests

# Création du container
$ docker run --name my-postgres -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=store -d postgres

# Connexion à la base de données
$ docker run -it --link my-postgres:postgres --rm postgres sh -c 'exec psql -h "$POSTGRES_PORT_5432_TCP_ADDR" -p "$POSTGRES_PORT_5432_TCP_PORT" -U store'

 store=# -- Création du jeu de test
 store=# create table bigtable (id serial, gender character(1), city varchar(50), country varchar(2));
 store=# create index on bigtable (gender);
 store=# create index on bigtable (city);
 store=# create index on bigtable (country);

 store=# insert into bigtable (gender, city)
 select
 ('{M,F}'::text[])[ceil(random()*2)],
 ('{Paris,Lille,Grenoble,London,Madrid,Berlin,Copenhague}'::text[])[ceil(random()*7)]
 from
 generate_series(1, 10000000);

 store=# update bigtable set country = 'fr' where city in ('Paris', 'Lille', 'Grenoble');
 store=# update bigtable set country = 'gb' where city = 'London';
 store=# update bigtable set country = 'es' where city = 'Madrid';
 store=# update bigtable set country = 'de' where city = 'Berlin';
 store=# update bigtable set country = 'dk' where city = 'Copenhague';

Installation de Cubes

Pour démarrer avec OLAP, il ne nous faut pas grand chose, Cubes s’installe simplement via Pypi. Il nous faudra également SQLAlchemy et psycopg2 pour se connecter à la base de données PostgreSQL. Je mets tout ça dans le fichier requirements.txt et j’y ajoute flask, qui sera nécessaire pour démarrer le serveur HTTP.

cubes
SQLAlchemy
psycopg2
flask

Il faudra ensuite un fichier pour définir le modèle de notre cube, au format json. Je l’avoue c’est la partie la plus compliquée et elle m’a pris bien plus de 15 minutes; on ne doit pas être loin des 2 heures en fait…

{
    "dimensions": [
        {
            "name": "profil",
            "levels": [
                {
                    "name": "gender",
                    "attributes": ["gender"]
                }
            ]
        },
        {
            "name": "geography",
            "levels": [
                {"name": "country", "attributes": ["country"]},
                {"name": "city", "attributes": ["city"]}
            ],
            "hierarchies": [
                {"name":"city", "levels":["country", "city"]},
                {"name":"country", "levels":["country"]}
            ],
            "default_hierarchy_name": "city"
        }
    ],
    "cubes": [
        {
            "name": "bigtable",
            "dimensions": ["profil", "geography"],
            "aggregates": [
                {
                    "name": "record_count",
                    "function": "count"
                }
            ],
            "mappings": {
                "profil.gender": "gender",
                "geography.city": "city",
                "geography.country": "country"
            }
        }
    ]
 }

 

On va ensuite écrire une première agrégation qui va nous permettre de valider l’installation et de voir un peu comment interroger notre serveur OLAP en Python.

from cubes import Workspace, PointCut, Cell, create_formatter

workspace = Workspace()
workspace.register_default_store("sql", url="postgresql://store:secret@postgres/store")
workspace.import_model("model.json")

browser = workspace.browser("bigtable")

cut = PointCut("profil", ["M"])
cell = Cell(browser.cube, cuts = [cut])

result = browser.aggregate(cell, drilldown=["profil", "geography"])

formatter = create_formatter("text_table")
print formatter.format(result, "geography")

Il ne reste plus qu’à créer le container pour l’exécuter

FROM python:2-onbuild
CMD [ "python", "./aggregate.py" ]

 

Et pour finir, le fichier docker-compose.yml fera le lien entre nos deux containers

olap:
build: .
external_links:
    - my-postgres:postgres

 

Voyons le résultat

$ docker-compose build
$ docker-compose up
Recreating olap_olap_1...
Attaching to olap_olap_1
olap_1 | de 713537
olap_1 | dk 713865
olap_1 | es 713545
olap_1 | fr 2143307
olap_1 | gb 714955
olap_1 |
olap_olap_1 exited with code 0
Gracefully stopping... (press Ctrl+C again to force)

 

Le serveur HTTP

Oui, au début j’ai bien dit que Cubes est un serveur HTTP OLAP, mais point de HTTP jusqu’ici, juste des scripts, et encore des scripts…

Pour démarrer le serveur HTTP, il nous faut encore 2 choses, créer un fichier de configuration pour slicer et modifier le fichier docker-compose.yml

[workspace]
log_level: info

[server]
host: 0.0.0.0
port: 5000
reload: yes
prettyprint: yes 

[store]
type: sql
url: postgresql://store:secret@postgres/store

[models]
main: model.json
olap:
    build: .
    command: slicer serve slicer.ini
    ports:
        - "5000:5000"
    external_links:
        - my-postgres:postgres

On peut maintenant démarrer le serveur HTTP…

gluchet@localhost OLAP $ docker-compose up
Recreating olap_olap_1...
Attaching to olap_olap_1
olap_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

…et essayer une ou deux requêtes

$ curl "http://192.168.59.103:5000/cube/bigtable/aggregate"
{
    "summary": {
        "record_count": 10000000
    },
    "remainder": {},
    "cells": [],
    "aggregates": [
        "record_count"
    ],
    "cell": []
}

$ curl "http://192.168.59.103:5000/cube/bigtable/aggregate?drilldown=profil"
{
    "summary": {
        "record_count": 10000000
    },
    "remainder": {},
    "cells": [
        {
            "profil": "F",
            "record_count": 5000791
        },
        {
            "profil": "M",
            "record_count": 4999209
        }
    ],
    "total_cell_count": 2,
    "aggregates": [
        "record_count"
    ],
    "cell": [],
    "levels": {
        "profil": [
            "gender"
        ]
    }
}

Resources

 

About the Author: Guillaume Luchet

Guillaume Luchet est Directeur de la R&D et Lead Développeur chez Bilendi Technology, entrepreneur et développeur freelance.