Skip to main content

A redis-like cache based on POSIX shared memory.

Project description

Shmedis

Shmedis is a high-performance, in-memory caching library for Python that behaves like Redis but leverages shared memory to allow for efficient data sharing between processes on the same machine. It provides a simple, dictionary-like interface for storing and retrieving Python objects, making it an ideal solution for inter-process communication and fast-caching scenarios.

Features

  • Redis-like API: Offers a familiar and intuitive API with methods like set, get, and delete.
  • Shared Memory Backend: Utilizes multiprocessing.SharedMemory for extremely fast data access across different processes, eliminating the need for socket communication or serialization overhead.
  • Process-Safe: Implements robust process-safe locking mechanisms using pthread read-write locks ( pthread_rwlock_t), ensuring data integrity during concurrent access.
  • Efficient Memory Management: Employs a sophisticated memory allocation strategy using a skip list to manage free memory fragments, reducing fragmentation and optimizing memory usage.
  • Fast Key Lookups: Uses a custom hash table implementation (IndexHashTable) with xxhash for rapid key retrieval.
  • Built-in Expiration: Supports setting an expiration time for cached items.

How It Works

Shmedis orchestrates several low-level components to create a high-performance cache:

  1. Shared Memory Block: A large chunk of memory is allocated using multiprocessing.SharedMemory, which is accessible by multiple processes. This block is partitioned into several areas:

    • Metadata Area: Stores essential information about the cache, such as total size, offsets to other data structures, and the heap top.
    • Lock Area: Contains a process-shared read-write lock (RawProcessRwLock) to manage concurrent access.
    • Index Hash Table: A fixed-size hash table that maps keys to the memory addresses of their corresponding values.
    • Free-Memory Skip List: A skip list that keeps track of available memory fragments, enabling efficient allocation and deallocation.
    • Data Area: The primary region where the actual key-value data is stored.
  2. Data Storage (SDS): Values are stored using a custom dynamic string-like structure called SDS (Simple Dynamic String), which is inspired by Redis's own string implementation. Each SDS object contains metadata like capacity, length, and an optional expiration timestamp.

  3. Memory Allocation and Deallocation:

    • When a new item is added, Shmedis first attempts to find a suitable free memory block from the skip list.
    • If no appropriately sized fragment is available, it allocates a new block from the top of the heap.
    • When an item is deleted, its memory block is returned to the skip list. The system will also try to merge it with any adjacent free blocks to create larger contiguous free spaces.
  4. Concurrency: To prevent race conditions and ensure data consistency, all read and write operations are protected by a pthread_rwlock_t. This allows multiple processes to read data simultaneously but ensures that write operations have exclusive access.

Installation

You can install Shmedis directly from PyPI:

pip install shmedis

Usage

Using Shmedis is straightforward. Here's a basic example:

import os
from multiprocessing import Process
from shmedis import Shmedis


def writer_process(cache_name):
    # Connect to the same shared memory cache
    cache = Shmedis(name=cache_name)

    # Set some values
    cache.set(b'mykey', b'hello world')
    cache.set('another_key', 'with a string value'.encode('utf-8'), ex=5)  # Expires in 5 seconds
    print(f"[Writer PID: {os.getpid()}] Set two keys.")


def reader_process(cache_name):
    # Connect to the same shared memory cache
    cache = Shmedis(name=cache_name)

    # Get a value
    value = cache.get(b'mykey')
    if value:
        print(f"[Reader PID: {os.getpid()}] Got value: {value.decode('utf-8')}")

    # Get another value
    another_value = cache.get('another_key')
    if another_value:
        print(f"[Reader PID: {os.getpid()}] Got another_value: {another_value.decode('utf-8')}")


if __name__ == "__main__":
    cache_name = "my_shared_cache"

    # Create the cache instance in the main process.
    # This will create the shared memory block.
    main_cache = Shmedis(name=cache_name, size=1024 * 1024)  # 1MB cache

    # Create and start writer and reader processes
    writer = Process(target=writer_process, args=(cache_name,))
    reader = Process(target=reader_process, args=(cache_name,))

    writer.start()
    writer.join()

    reader.start()
    reader.join()

    # Clean up the shared memory
    main_cache.shm.unlink()

API Reference

Shmedis(name: str = "default", size: int = 1 << 30)

  • name: A unique name for the shared memory block.
  • size: The total size in bytes to allocate for the cache.

Methods

  • set(key: bytes | str, value: bytes, ex: int = 0, nx: bool = False): Stores a key-value pair.

    • key: The key for the item (can be str or bytes).
    • value: The value to store (bytes).
    • ex: Optional expiration time in seconds.
    • nx: Set key if not exist or Fail

    Return True if set success.

  • get(key: bytes | str) -> Optional[bytes]: Retrieves the value for a given key. Returns None if the key does not exist or has expired.

  • delete(key: bytes | str): Removes a key-value pair from the cache and frees the associated memory.

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository.

License

This project is licensed under the APACHE-2.0 License. See the LICENSE-APACHE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shmedis-0.3.8.tar.gz (19.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shmedis-0.3.8-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file shmedis-0.3.8.tar.gz.

File metadata

  • Download URL: shmedis-0.3.8.tar.gz
  • Upload date:
  • Size: 19.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for shmedis-0.3.8.tar.gz
Algorithm Hash digest
SHA256 e99d2211b02189e3c08db7f245b5f240bb667737b8a53a1c1ecbc9c1abf373c2
MD5 7b73c46d0b9e76b16f2692b15950a498
BLAKE2b-256 05efa65bde2694dc0db9ad4f4e1c5e430721344d0fee0e285388aafc6d34b4d5

See more details on using hashes here.

File details

Details for the file shmedis-0.3.8-py3-none-any.whl.

File metadata

  • Download URL: shmedis-0.3.8-py3-none-any.whl
  • Upload date:
  • Size: 17.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for shmedis-0.3.8-py3-none-any.whl
Algorithm Hash digest
SHA256 8755ce881440e6e26dba9a33f9ab38a1eb7b325a7fd82d3e1c45182e76aa0075
MD5 3618e5d3a105c7bd73712eae0daca683
BLAKE2b-256 d793cdecc6083c83b15a764f109d5b3c5a41d4fdd0b7fa082e903bb8abf7f5b5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page