Skip to content

feat: add raw_data mode to skip value serialization (re-implement #131) #313

Closed

Description

@jaysonsantos

Problem

Some applications already use the 32-bit memcached flags field for their own data. When bmemcached reads one of these keys, it interprets the flags bits as its own type flags (object, integer, long, compressed, binary). The client then tries to decompress, unpickle, or int() the value, and the read fails.

PR #131 by @rkday proposed a raw_data mode for this case. That PR targets 2018 code and does not apply to the current main. This issue describes a re-implementation on main that keeps the public API compatible.

Scope (option A)

Add an opt-in raw_data=False keyword argument. If raw_data is True:

  • Reads return the stored bytes value. The client ignores the flags field.
  • Writes send the value without serialization and with flags=0.
  • The client does not compress values.

The default raw_data=False keeps the current behavior. The flags bits and the stored data format do not change.

Out of scope: a per-call flags= argument on set/add/replace. In raw mode, a write overwrites the flags that another application set on the key. The docs must state this.

Implementation

  1. Add raw_data=False as the last parameter in these signatures:
    • Protocol.__init__ in bmemcached/protocol.py
    • ClientMixin.__init__ in bmemcached/client/mixin.py
    • DistributedClient.__init__ in bmemcached/client/distributed.py
  2. Keep raw_data after tls_context. DistributedClient passes arguments to super().__init__ by position, and users can also call the clients by position.
  3. In DistributedClient.__init__, pass raw_data to super().__init__.
  4. In ClientMixin, store self.raw_data. Pass it to each Protocol in set_servers.
  5. In Protocol.serialize, add a raw-mode branch before the type checks:
    • If compress_level > 0, raise MemcachedException. Compression is not possible in raw mode.
    • If the value is not bytes, raise TypeError. Do not encode str or int values.
    • Return (0, value).
  6. In Protocol.deserialize, if self.raw_data is True, return value before the flags checks.
  7. Add docstrings for raw_data in ClientMixin (and Protocol if it has parameter docs). State that the default compress_level=-1 means "no compression" in raw mode.
  8. Update the docs in docs/ with a short usage section.

Tests

Add tests in test/:

  • Raw round-trip: set and get of a bytes value with Client(raw_data=True).
  • Flags collision: write a value with flags 0x8 (compressed) or 0x1 (object) through a normal client or a raw socket. Read it with raw_data=True. The result must be the original bytes.
  • set of a str or int value in raw mode raises TypeError.
  • set with compress_level=9 in raw mode raises MemcachedException.
  • A large value (more than COMPRESSION_THRESHOLD) in raw mode is stored without compression.
  • get_multi and set_multi in raw mode.
  • DistributedClient(raw_data=True) round-trip.
  • Default clients keep the current behavior. The existing suite covers this.

Verification

Run these commands:

  • pytest -s
  • ruff check .
  • ruff format --check .

Pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions