Python implementation of Gorilla time series compression

Overview

Gorilla Time Series Compression

This is an implementation (with some adaptations) of the compression algorithm described in section 4.1 (Time series compression) of [1] (read the paper here).

Gorilla compression is lossless.

This library can be used in three ways:

  • Timestamps only compression.
  • Values only compression (useful for regular time series compression).
  • Timestamp-Value pairs compression (useful for irregular time series compression).

In all three cases, the result of the encoding process is a dict with everything necessary for decoding (see Usage for examples). If you want to use this library for compressed message exchanges, you can serialize the result of the encoding process as you like (JSON, protobuf, etc.)

This implementation is based on section 4.1 of [1] and on the Facebook's open source implementation [2] (which have some differences).

Differences from the original paper

  • Timestamps or values can be encoded separately.
  • The header (with an aligned timestamp) at the beginning (64 bits) of the message is not encoded.
  • The float format can be specified (f64, f32, f16) to optimize the size of certain fields.

Installation

To install the latest release:

$ pip install gorillacompression

You can also build a local package and install it:

$ make build
$ pip install dist/*.whl

Usage

Import gorillacompression module.

>>> import gorillacompression as gc

Data to encode.

>>> timestamps = [1628164645, 1628164649, 1628164656, 1628164669]
>>> values = [18.95, 18.91, 17.01, 14.05]
>>> pairs = list(zip(timestamps, values))
>>> pairs
[(1628164645, 18.95), (1628164649, 18.91), (1628164656, 17.01), (1628164669, 14.05)]

In the three scenarios of compression (timestamps, values, pairs), you can use:

  • encode_all to encode all elements or encode_next to encode element by element.
  • decode_all to decode everything.

encode_next returns True if the element has been encoded correctly, False if the element has not been encoded accompanied by a warning explaining the reason.

Timestamps only compression

The expected input timestamp is a POSIX timestamp less than 2147483647 ('January 19, 2038 04:14:07'). The delta between two successive timestamps must be greater than or equal to 0.

You can use encode_all to encode all timestamps:

>>> content = gc.TimestampsEncoder.encode_all(timestamps)
>>> content
{'encoded': b'\xc2\x17\xa4K\x08\xa1Q@', 'nb_timestamps': 4}
>>> gc.TimestampsDecoder.decode_all(content)
[1628164645, 1628164649, 1628164656, 1628164669]

Or you can use encode_next to encode one by one:

>>> ts_encoder = gc.TimestampsEncoder()
>>> for ts in timestamps:
...     ts_encoder.encode_next(ts)
>>> content = ts_encoder.get_encoded()
>>> content
{'encoded': b'\xc2\x17\xa4K\x08\xa1Q@', 'nb_timestamps': 4}
>>> gc.TimestampsDecoder.decode_all(content)
[1628164645, 1628164649, 1628164656, 1628164669]

Values only compression

You can use encode_all to encode all values:

>>> content = gc.ValuesEncoder.encode_all(values)
>>> content
{'encoded': b'@2\xf333333\xe7f\xf1\xbco\x1b\xc6\xee\xc7\xeaz\x9e\xa7\xa9\xeb\xaf^\x8d\x8bb\xd8\xb6,\x80', 'nb_values': 4, 'float_format': 'f64'}
>>> gc.ValuesDecoder.decode_all(content)
[18.95, 18.91, 17.01, 14.05]

Or you can use encode_next to encode one by one:

>>> values_encoder = gc.ValuesEncoder()
>>> for v in values:
...     values_encoder.encode_next(v)
>>> content = values_encoder.get_encoded()
>>> content
{'encoded': b'@2\xf333333\xe7f\xf1\xbco\x1b\xc6\xee\xc7\xeaz\x9e\xa7\xa9\xeb\xaf^\x8d\x8bb\xd8\xb6,\x80', 'nb_values': 4, 'float_format': 'f64'}
>>> gc.ValuesDecoder.decode_all(content)
[18.95, 18.91, 17.01, 14.05]

Timestamp-Value pairs compression

You can use encode_all to encode all pairs:

>>> content = gc.PairsEncoder.encode_all(pairs)
>>> content
{'encoded': b'\xc2\x17\xa4J\x80e\xe6ffffg\x08\xe7f\xf1\xbco\x1b\xc6\xd0\xb7c\xf5=OS\xd4\xf5\xa2\xeb\xd7\xa3b\xd8\xb6-\x8b ', 'nb_pairs': 4, 'float_format': 'f64'}
>>> gc.PairsDecoder.decode_all(content)
[(1628164645, 18.95), (1628164649, 18.91), (1628164656, 17.01), (1628164669, 14.05)]

Or you can use encode_next to encode one by one:

>>> pairs_encoder = gc.PairsEncoder()
>>> for (ts, v) in pairs:
...     pairs_encoder.encode_next(ts, v)
>>> content = pairs_encoder.get_encoded()
>>> content
{'encoded': b'\xc2\x17\xa4J\x80e\xe6ffffg\x08\xe7f\xf1\xbco\x1b\xc6\xd0\xb7c\xf5=OS\xd4\xf5\xa2\xeb\xd7\xa3b\xd8\xb6-\x8b ', 'nb_pairs': 4, 'float_format': 'f64'}
>>> gc.PairsDecoder.decode_all(content)
[(1628164645, 18.95), (1628164649, 18.91), (1628164656, 17.01), (1628164669, 14.05)]

Gorilla compression algorithm explanation

Below is a brief explanation of the implemented method. (Refer to [1] section 4.1 (read the paper here) for the original explanation)

Timestamps compression

  • The first timestamp is encoded in a fixed number of bits.
  • The following timestamps are encoded as follows:
  (a) Calculate the delta of delta
          D = (t_n − t_(n−1)) − (t_(n−1) − t_(n−2))
  (b) If D is zero, then store a single ‘0’ bit
  (c) If D is between [-63, 64], store ‘10’ followed by the value (7 bits)
  (d) If D is between [-255, 256], store ‘110’ followed by the value (9 bits)
  (e) if D is between [-2047, 2048], store ‘1110’ followed by the value (12 bits)
  (f) Otherwise store ‘1111’ followed by D using 32 bits

Values compression

Notation

    n bits:
    +---- n ----+
    |           |
    +---- n' ---+

    n bytes:
    +==== n ====+
    |           |
    +==== n' ===+

    `~` in place of `n` means a variable number of bytes or bits.

    When it makes sense, n refers to the default value, and n' to the variable containing the value.

This explanation corresponds to the case of float format f64, for the other formats (f32, f16), the size of some fields is different (refer to the code for more details).

  1. The first value is stored with no compression.
    +======================= 8 =======================+
    |  First value (IEEE 754, binary64, Big Endian)   |
    +======================= 8 =======================+
  1. If XOR with the previous is zero (same value), store single ‘0’ bit.
    +-- 1 --+
    |   0   |
    +-- 1 --+
  1. When XOR is non-zero, calculate the number of leading and trailing zeros in the XOR, store bit ‘1’ followed by either a) or b):
  • (a) (Control bit ‘0’) If the block of meaningful bits falls within the block of previous meaningful bits*, i.e., there are at least as many leading zeros and as many trailing zeros as with the previous value, use that information for the block position and just store the meaningful XORed value*.
    +--- 2 ---+--- length of the meaningful XORed value ---+
    |   10    |         [meaningful XORed value]           |
    +--- 2 ---+--- length of the meaningful XORed value ---+
  • (b) (Control bit ‘1’) Store the length of the number of leading zeros in the next 5 bits, then store the length of the meaningful XORed value in the next 6 bits. Finally store the meaningful bits of the XORed value.
    +--- 2 ---+------------- 5 -------------+------------------- 6 ------------------+--- length of the meaningful XORed value ---+
    |   11    |   number of leading zeros   |   length of the meaningful XORed value |         [meaningful XORed value]           |
    +--- 2 ---+------------- 5 -------------+------------------- 6 ------------------+--- length of the meaningful XORed value ---+
  1. After the compression of the last value, if the length of the bitarray is not a multiple of 8, the few remaining bits are padded with zero.
    +---- n ----+
    |   0...0   |
    +---- n ----+

    n < 8

(*) The terms "meaningful bits" and "meaningful XORed value" used in the original paper may be confusing.

  • In case (b), "meaningful XORed value" is a value with absolutely no leading and trailing zero.
  • In case (a), "meaningful XORed value" is the XORed value striped off same amount of leading and trailing zeroes as previous value delta. The meaningful bits in this case may still contain some leading and trailing zeroes.

Timestamp-Value pairs compression

The encoding of a pair is the encoding of the timestmap followed by the encoding of the value.

Contribute

Please, open issues. PR are very welcome!

$ git clone https://github.com/ghilesmeddour/gorilla-time-series-compression.git
$ cd gorilla-time-series-compression
make format
make dead-code-check
make test
make type-check
make coverage
make build

TODOs

  • Add more unit tests (f32 and f16 float formats are currently not tested).
  • Add profiling, benchmarks, etc.
  • Improve doc, docstring, etc.

Other implementations

References

[1] Pelkonen, T., Franklin, S., Teller, J., Cavallaro, P., Huang, Q., Meza, J., & Veeraraghavan, K. (2015). Gorilla: A fast, scalable, in-memory time series database. Proceedings of the VLDB Endowment, 8(12), 1816-1827.

You might also like...
Compute the fair market value (FMV) of staking rewards at time of receipt.

tendermint-tax A tool to help calculate the tax liability of staking rewards on Tendermint chains. Specifically, this tool calculates the fair market

This is a python table of data implementation with styles, colors
This is a python table of data implementation with styles, colors

Table This is a python table of data implementation with styles, colors Example Table adapts to the lack of data Lambda color features Full power of l

A simple python implementation of Decision Tree.

DecisionTree A simple python implementation of Decision Tree, using Gini index. Usage: import DecisionTree node = DecisionTree.trainDecisionTree(lab

A fast Python implementation of Ac Auto Mechine

A fast Python implementation of Ac Auto Mechine

✨ Voici un code en Python par moi, et en français qui permet d'exécuter du Javascript en Python.
✨ Voici un code en Python par moi, et en français qui permet d'exécuter du Javascript en Python.

JavaScript In Python ❗ Voici un code en Python par moi, et en français qui permet d'exécuter du Javascript en Python. 🔮 Une vidéo pour vous expliquer

Simple python module to get the information regarding battery in python.
Simple python module to get the information regarding battery in python.

Battery Stats A python3 module created for easily reading the current parameters of Battery in realtime. It reads battery stats from /sys/class/power_

Python @deprecat decorator to deprecate old python classes, functions or methods.

deprecat Decorator Python @deprecat decorator to deprecate old python classes, functions or methods. Installation pip install deprecat Usage To use th

A python package containing all the basic functions and classes for python. From simple addition to advanced file encryption.
A python package containing all the basic functions and classes for python. From simple addition to advanced file encryption.

A python package containing all the basic functions and classes for python. From simple addition to advanced file encryption.

Find dependent python scripts of a python script in a project directory.

Find dependent python scripts of a python script in a project directory.

Comments
  • OverflowError: unsigned integer not in range(0, 64), got 64

    OverflowError: unsigned integer not in range(0, 64), got 64

    I got this message when trying to encode values :

    
    values = [-0.39263690585168304, -0.39263690585168304, -0.39263690585168304, 0.450762617155903, 0.450762617155903, 0.450762617155903, -0.284155454538896]
    values_encoder = gc.ValuesEncoder()
    for v in values:
        print(v)
        values_encoder.encode_next(v)
    

    This output :

    -0.39263690585168304
    -0.39263690585168304
    -0.39263690585168304
    0.450762617155903
    0.450762617155903
    0.450762617155903
    -0.284155454538896
    
    ---------------------------------------------------------------------------
    OverflowError                             Traceback (most recent call last)
    /tmp/ipykernel_9713/2845470605.py in <module>
         10 for v in values:
         11     print(v)
    ---> 12     values_encoder.encode_next(v)
    
    ~/.local/lib/python3.8/site-packages/gorillacompression/values/encode.py in encode_next(self, value)
        118             # Encode length of the meaningful XORed value.
        119             length_of_the_meaningful_xored_value = self.n_bits_value - n_leading_zeros - n_trailing_zeros
    --> 120             self.bit_array += util.int2ba(
        121                 length_of_the_meaningful_xored_value,
        122                 length=self.n_bits_length_of_the_meaningful_xored_value)
    
    ~/.local/lib/python3.8/site-packages/bitarray/util.py in int2ba(__i, length, endian, signed)
        267             raise OverflowError("unsigned integer not positive, got %d" % __i)
        268         if length and __i >= (1 << length):
    --> 269             raise OverflowError("unsigned integer not in range(0, %d), "
        270                                 "got %d" % (1 << length, __i))
        271 
    
    OverflowError: unsigned integer not in range(0, 64), got 64
    
    

    Version 0.0.1

    same problem with encode_all

    opened by Jeanbouvatt 3
  • Make encode_all a class method, or add a float_format parameter to the method

    Make encode_all a class method, or add a float_format parameter to the method

    Currently, encode_all is a static method of the ValuesEncoder class. Its usage can be misleading when a float format different from f64 is used:

    content = gc.ValuesEncoder(float_format='f16').encode_all(sequence) content['float_format']

    The result is 'f64', since the method does not take into account the parameters of the instance of ValuesEncoder used to invoke the method.

    I would suggest to make encode_all a class method, and/or add a float_format parameter to the static method to avoid this potential trouble.

    opened by mgoeminne 2
  • Encode all unable to use 'f32' or 'f16' formats

    Encode all unable to use 'f32' or 'f16' formats

    Hi,

    In line 148 of the file encode.py, you seem to have made a small error in the sense that you initialize a default ValuesEncoder. This means that if you are trying to do encoding in a different format (say 'f32') this will be superceded by the default. In other words, this means that in the current pip implementation of gorillacompression it is impossible to do anything but 'f64' compression unless you use encode_next manually.

    Simple fix would be to initialize that value encoder in that function using the values from "self.". (Alternatively, I can make a pull request).

    Regards, Zack

    opened by HeatPhoenix 2
Releases(v0.2.1)
Owner
Ghiles Meddour
Data Analyst at Munic
Ghiles Meddour
Local backup made easy, with Python and shutil

KTBackup BETA Local backup made easy, with Python and shutil Features One-command backup and restore Minimalistic (only using stdlib) Convenient direc

kelptaken 1 Dec 27, 2021
Standard implementations of FedLab and its provided benchmarks.

FedLab-benchmarks This repo contains standard implementations of FedLab and its provided benchmarks. Currently, following algorithms or benchrmarks ar

SMILELab-FL 104 Dec 05, 2022
A Python class for checking the status of an enabled Minecraft server

mcstatus provides an easy way to query Minecraft servers for any information they can expose. It provides three modes of access (query, status and ping), the differences of which are listed below in

Nathan Adams 1.1k Jan 06, 2023
A library for interacting with Path of Exile game and economy data, and a unique loot filter generation framework.

wraeblast A library for interfacing with Path of Exile game and economy data, and a set of item filters geared towards trade league players. Filter Ge

David Gidwani 29 Aug 28, 2022
Monte Carlo simulation of 3G rules

mc3g Monte Carlo simulation of 3G rules This project contains the Python code to do simulations of events according to the 3G rule (in German: "Geimpf

Jan Christoph Terasa 4 Nov 01, 2021
Factoral Methods using two different method

Factoral-Methods-using-two-different-method Here, I am finding the factorial of a number by using two different method. The first method is by using f

Sachin Vinayak Dabhade 4 Sep 24, 2021
Random Number Generator

Application for generating a random number.

Michael J Bailey 1 Oct 12, 2021
Entropy-controlled contexts in Python

Python module ordered ordered module is the opposite to random - it maintains order in the program. import random x = 5 def increase(): global x

HyperC 36 Nov 03, 2022
Simple integer-valued time series bit packing

Smahat allows to encode a sequence of integer values using a fixed (for all values) number of bits but minimal with regards to the data range. For example: for a series of boolean values only one bit

Ghiles Meddour 7 Aug 27, 2021
Genart - Generate random art to sell as nfts

Genart - Generate random art to sell as nfts Usage git clone

Will 13 Mar 17, 2022
cpp20.py is a Python script to compile C++20 code using modules.

cpp20.py is a Python script to compile C++20 code using modules. It browses the source files to determine their dependencies. Then, it compiles then in order using the correct flags.

Julien VERNAY 6 Aug 26, 2022
This is a python table of data implementation with styles, colors

Table This is a python table of data implementation with styles, colors Example Table adapts to the lack of data Lambda color features Full power of l

Урядов Алексей 5 Nov 09, 2021
Generates a random prnt.sc link and display image.

Generates a random prnt.sc link and display image.

Emirhan 3 Oct 08, 2021
a demo show how to dump lldb info to ida.

用一个demo来聊聊动态trace 这个仓库能做什么? 帮助理解动态trace的思想。仓库内的demo,可操作,可实践。 动态trace核心思想: 动态记录一个函数内每一条指令的执行中产生的信息,并导入IDA,用来弥补IDA等静态分析工具的不足。 反编译看一下 先clone仓库,把hellolldb

25 Nov 28, 2022
Dependency injection lib for Python 3.8+

PyDI Dependency injection lib for python How to use To define the classes that should be injected and stored as bean use decorator @component @compone

Nikita Antropov 2 Nov 09, 2021
Astvuln is a simple AST scanner which recursively scans a directory, parses each file as AST and runs specified method.

Astvuln Astvuln is a simple AST scanner which recursively scans a directory, parses each file as AST and runs specified method. Some search methods ar

Bitstamp Security 7 May 29, 2022
Rabbito is a mini tool to find serialized objects in input values

Rabbito-ObjectFinder Rabbito is a mini tool to find serialized objects in input values What does Rabbito do Rabbito has the main object finding Serial

7 Dec 13, 2021
Implementing C++ Semantics in Python

Implementing C++ Semantics in Python

Tamir Bahar 7 May 18, 2022
More routines for operating on iterables, beyond itertools

More Itertools Python's itertools library is a gem - you can compose elegant solutions for a variety of problems with the functions it provides. In mo

2.9k Jan 06, 2023
Macro recording and metaprogramming in Python

macro-kit is a package for efficient macro recording and metaprogramming in Python using abstract syntax tree (AST).

8 Aug 31, 2022