The most surprising thing about pylibmc is that its default configuration is often the source of its most baffling performance issues, not its actual connection to Memcached servers.

Let’s see pylibmc in action. Imagine you have a Memcached server running on 192.168.1.100:11211.

import pylibmc

# Basic connection
mc = pylibmc.Client(['192.168.1.100:11211'], binary=True)

# Setting and getting data
mc.set('my_key', 'my_value')
value = mc.get('my_key')
print(value) # Output: b'my_value'

# Using it for caching
def get_user_data(user_id):
    cache_key = f'user:{user_id}'
    user_data = mc.get(cache_key)
    if user_data:
        print(f"Cache hit for user {user_id}")
        return user_data
    else:
        print(f"Cache miss for user {user_id}")
        # Simulate fetching from a database
        user_data = {'id': user_id, 'name': f'User {user_id}', 'email': f'user{user_id}@example.com'}
        mc.set(cache_key, str(user_data), time=300) # Cache for 5 minutes
        return user_data

print(get_user_data(123))
print(get_user_data(123))

The core problem pylibmc solves is providing a fast, in-memory key-value store for caching frequently accessed data, reducing load on slower persistent storage like databases. Internally, pylibmc is a Python wrapper around libmemcached, a battle-tested C library. This means it’s not just copying data back and forth; it’s leveraging highly optimized C code for network communication and Memcached protocol handling.

The pylibmc.Client constructor is where the magic (and potential pain) happens. You pass it a list of server addresses and a host of optional arguments that control its behavior.

mc = pylibmc.Client(
    ['192.168.1.100:11211', '192.168.1.101:11211'],
    binary=True,
    behaviors={
        'tcp_nodelay': True,
        'ketama': True,
        'retry_timeout': 2000, # milliseconds
        'dead_timeout': 60000, # milliseconds
        'connect_timeout': 1000, # milliseconds
        'send_timeout': 1000, # milliseconds
        'receive_timeout': 1000, # milliseconds
        'no_block': True
    }
)

The servers argument is straightforward: a list of host:port strings. binary=True is crucial for performance and correctness, enabling the binary Memcached protocol which is more efficient and supports data types beyond simple strings.

The behaviors dictionary is where you tune libmemcached’s internal workings. tcp_nodelay=True disables the Nagle algorithm, which can reduce latency for small, frequent writes by sending packets immediately. ketama=True enables consistent hashing (using the Ketama algorithm), which is vital for distributing keys evenly across multiple Memcached servers and minimizing cache invalidation when servers are added or removed.

retry_timeout and dead_timeout control how libmemcached handles transient network issues. retry_timeout (in milliseconds) is how long it will wait before retrying a failed operation to a server. dead_timeout (in milliseconds) is how long a server is considered "dead" and will be excluded from operations after repeated failures. Setting these too low can lead to premature server exclusion, while too high can make the application unresponsive during network glitches.

connect_timeout, send_timeout, and receive_timeout (all in milliseconds) are critical for preventing your application from hanging indefinitely if a Memcached server becomes unresponsive. no_block=True enables non-blocking I/O, which is essential for high-performance applications that can’t afford to wait for slow network operations.

The pylibmc client doesn’t inherently manage connections in a pool like some other libraries. Instead, libmemcached manages a set of connections internally, and pylibmc exposes these behaviors. When you call mc.get(), pylibmc asks libmemcached to find an appropriate connection to a server (based on hashing and server status) and send the request. If a connection is stale or the server is temporarily unavailable, libmemcached will attempt to re-establish it or use another server if configured.

One subtle point is how pylibmc handles data serialization. By default, it doesn’t do any Python object serialization. You need to ensure that what you set can be directly stored by Memcached (bytes, strings) and that what you get is interpreted correctly. For complex Python objects, you’ll typically use json.dumps before set and json.loads after get, or a library like pickle (though pickle has security implications and is generally discouraged for data exchanged with untrusted sources).

The pylibmc client’s approach to server selection and failure handling is managed by the underlying libmemcached library, which uses a combination of consistent hashing (if ketama is enabled) and configurable timeouts to determine which server to use and how to react to network problems. If a server is marked as "dead" due to repeated timeouts, libmemcached will stop sending requests to it until its dead_timeout expires and it’s re-evaluated.

The next thing you’ll likely encounter is optimizing serialization and deserialization for your cached data.

Want structured learning?

Take the full Memcached course →