{"id":15056955,"url":"https://github.com/arindas/memcached-ebpf-proxy-cache","last_synced_at":"2026-03-07T01:02:39.973Z","repository":{"id":247202609,"uuid":"821237249","full_name":"arindas/memcached-ebpf-proxy-cache","owner":"arindas","description":"Intercept and serve memcached requests from eBPF.","archived":false,"fork":false,"pushed_at":"2024-09-28T05:21:13.000Z","size":431,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T05:06:28.956Z","etag":null,"topics":["aya","ebpf","linux","memcached"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arindas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-28T05:34:41.000Z","updated_at":"2024-09-28T05:21:16.000Z","dependencies_parsed_at":"2025-02-16T14:38:21.308Z","dependency_job_id":"c39f6b4d-14ce-4cbe-825b-5b600ec54dad","html_url":"https://github.com/arindas/memcached-ebpf-proxy-cache","commit_stats":null,"previous_names":["arindas/memcached-ebpf-proxy-cache"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/arindas/memcached-ebpf-proxy-cache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arindas%2Fmemcached-ebpf-proxy-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arindas%2Fmemcached-ebpf-proxy-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arindas%2Fmemcached-ebpf-proxy-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arindas%2Fmemcached-ebpf-proxy-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arindas","download_url":"https://codeload.github.com/arindas/memcached-ebpf-proxy-cache/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arindas%2Fmemcached-ebpf-proxy-cache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30204452,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T19:07:06.838Z","status":"ssl_error","status_checked_at":"2026-03-06T18:57:34.882Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aya","ebpf","linux","memcached"],"created_at":"2024-09-24T21:59:13.513Z","updated_at":"2026-03-07T01:02:39.938Z","avatar_url":"https://github.com/arindas.png","language":"Rust","readme":"# memcached-ebpf-proxy-cache\n\nIntercept and serve `memcached` requests from eBPF.\n\n\u003e [!WARNING]  \n\u003e This project was made for learning purposes and is not meant for production usage.\n\n## Run\n\n### Prerequisites\n\nInstall bpf-linker:\n\n```sh\ncargo install bpf-linker\n```\n\nStart `memcached-ebpf-proxy-cache`:\n\n```bash\nRUST_LOG=debug cargo xtask run  -- --iface lo\n```\n\n## Overview\n\nmemcached-ebpf-proxy-cache maintains a smaller cache in the eBPF layer in front of memcached\nto service memcached requests right from the eBPF layer. _In theory_, for small GET requests (\u003c 250 bytes),\nthe networking stack adds a substantial overhead while the request is processed. eBPF allows a way\nto intercept and respond to the request even before it enters the networking stack.\n\n\u003e Note: The phrase _In theory_ is important here.\n\n`memcached-ebpf-proxy-cache` works with the `memcached` [binary protocol](https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped).\n\nIn NIC's which natively support the eBPF bytecode, these packets can be serviced directly from the NIC.\n\n![memcached-ebpf-proxy-cache-dataflow](./assets/memcached-ebpf-proxy-cache.drawio.png)\n\n\u003cp align=\"center\"\u003e\n\u003cb\u003eFig:\u003c/b\u003e Dataflow through \u003ccode\u003ememcached-ebpf-proxy-cache\u003c/code\u003e\n\u003c/p\u003e\n\n### Mode of operation\n\nWe maintain a [BPF_MAP_TYPE_ARRAY](https://docs.kernel.org/bpf/map_array.html) for storing the cache\nentries in our eBPF program. We use it to implement hashmap as follows:\n\n```\nCACHE[ fnv_1_a_hash(KEY) % CACHE_SIZE ] = (extra, KEY, VAL)\n```\n\nwhere `extra` refers to the `extra` bytes in a memcached `GET` response.\n\nLet's discuss the ingress and egress path seperately.\n\n#### Ingress Path\n\nThe ingress path involves the following eBPF programs:\n\n- `rx_filter`: Ingress entrypoint - filters memcached packets and lets other kind of packets pass through.\n  For memcached GET packets, it makes as tail call to `hash_key`. For set packets, it makes a tail call to `invalidate_cache`.\n- `hash_key`: Hashes the KEY in packet with the the [fnv_1_a_hash](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash) algorithm.\n  Looks up the entry at `hash % CACHE_SIZE`. If cache-hit, then tail call to `write_reply`, else let\n  the packet pass throught with xdp action `XDP_PASS`\n- `write_reply`: Modify packet: swap Eth header source dest, ip header source dest, UDP header source dest\n  and rewrite body with (extra, KEY, VAL) tuple. Send the packet back to the requesting NIC with `XDP_TX`.\n  This effectively processes the memcached request packet completey within our eBPF program.\n- `invalidate_cache`: Reached through tail call from `rx_filter` on SET request. Hash key, lookup cache entry at\n  index `hash % CACHE_SIZE` and mark it as invalid. Invalid cache entries are updated on the egress path in the\n  `update_cache` eBPF program.\n\n#### Egress Path\n\nThe egress path involves the following eBPF programs:\n\n- `tx_filter`: Egress entrypoint - filters memcached GET response packets and lets other kinds packets pass\n  through with `TC_ACT_OK`. Make a tail call to `update_cache` for GET response packets.\n- `update_cache`: Hashes KEY in reponse and looks up entry at `hash % CACHE_SIZE`. If entry is invalid, update\n  cache entry with (extra, KEY, VAL) tuple. Once done updating, let the packet pass through with `TC_ACT_OK`.\n\n## Testing\n\nStart tracing UDP packet traffic on port 11211 with the following command:\n\n```sh\nsudo tcpdump -Xi lo -n udp port 11211\n```\n\nFirst, test the control behaviour by running the test:\n\n```sh\ncargo test\n```\n\nWhich runs the following rust code:\n\n```rust\n\nlet get_client = memcache::connect(memcached_get_endpoint).unwrap();\n\nlet set_client = memcache::connect(memcached_set_endpoint).unwrap();\n\nconst KEY: \u0026str = \"key\";\nconst VAL: \u0026str = \"val\";\n\n// SET once. GET twice\n\nset_client.set(KEY, VAL, 10).unwrap();\n\nassert_eq!(get_client.get::\u003cString\u003e(KEY).unwrap().unwrap(), VAL);\nassert_eq!(get_client.get::\u003cString\u003e(KEY).unwrap().unwrap(), VAL);\n```\n\nRunning the test should yield a similar packet dump to the following packet dump:\n\n```\ntcpdump: verbose output suppressed, use -v[v]... for full protocol decode\nlistening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes\n21:20:40.642266 IP 127.0.0.1.58390 \u003e 127.0.0.1.11211: UDP, length 32\n        0x0000:  4500 003c 82b7 4000 4011 b9f7 7f00 0001  E..\u003c..@.@.......\n        0x0010:  7f00 0001 e416 2bcb 0028 fe3b 7152 0000  ......+..(.;qR..\n        0x0020:  0001 0000 800b 0000 0000 0000 0000 0000  ................\n        0x0030:  0000 0000 0000 0000 0000 0000            ............\n21:20:40.642572 IP 127.0.0.1.11211 \u003e 127.0.0.1.58390: UDP, length 38\n        0x0000:  4500 0042 42f5 4000 4011 f9b3 7f00 0001  E..BB.@.@.......\n        0x0010:  7f00 0001 2bcb e416 002e fe41 7152 0000  ....+......AqR..\n        0x0020:  0001 0000 810b 0000 0000 0000 0000 0006  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 312e 362e  ............1.6.\n        0x0040:  3134                                     14\n21:20:40.642829 IP 127.0.0.1.58390 \u003e 127.0.0.1.11211: UDP, length 35\n        0x0000:  4500 003f 82b8 4000 4011 b9f3 7f00 0001  E..?..@.@.......\n        0x0010:  7f00 0001 e416 2bcb 002b fe3e 7153 0000  ......+..+.\u003eqS..\n        0x0020:  0001 0000 8000 0003 0000 0000 0000 0003  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 6b65 79    ............key\n21:20:40.642951 IP 127.0.0.1.11211 \u003e 127.0.0.1.58390: UDP, length 39\n        0x0000:  4500 0043 42f6 4000 4011 f9b1 7f00 0001  E..CB.@.@.......\n        0x0010:  7f00 0001 2bcb e416 002f fe42 7153 0000  ....+..../.BqS..\n        0x0020:  0001 0000 8100 0000 0400 0000 0000 0007  ................\n        0x0030:  0000 0000 0000 0000 0000 0115 0000 0000  ................\n        0x0040:  7661 6c                                  val\n21:20:40.643061 IP 127.0.0.1.58390 \u003e 127.0.0.1.11211: UDP, length 32\n        0x0000:  4500 003c 82b9 4000 4011 b9f5 7f00 0001  E..\u003c..@.@.......\n        0x0010:  7f00 0001 e416 2bcb 0028 fe3b 7154 0000  ......+..(.;qT..\n        0x0020:  0001 0000 800b 0000 0000 0000 0000 0000  ................\n        0x0030:  0000 0000 0000 0000 0000 0000            ............\n21:20:40.643153 IP 127.0.0.1.11211 \u003e 127.0.0.1.58390: UDP, length 38\n        0x0000:  4500 0042 42f7 4000 4011 f9b1 7f00 0001  E..BB.@.@.......\n        0x0010:  7f00 0001 2bcb e416 002e fe41 7154 0000  ....+......AqT..\n        0x0020:  0001 0000 810b 0000 0000 0000 0000 0006  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 312e 362e  ............1.6.\n        0x0040:  3134                                     14\n21:20:40.643187 IP 127.0.0.1.58390 \u003e 127.0.0.1.11211: UDP, length 35\n        0x0000:  4500 003f 82ba 4000 4011 b9f1 7f00 0001  E..?..@.@.......\n        0x0010:  7f00 0001 e416 2bcb 002b fe3e 7155 0000  ......+..+.\u003eqU..\n        0x0020:  0001 0000 8000 0003 0000 0000 0000 0003  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 6b65 79    ............key\n21:20:40.643309 IP 127.0.0.1.11211 \u003e 127.0.0.1.58390: UDP, length 39\n        0x0000:  4500 0043 42f8 4000 4011 f9af 7f00 0001  E..CB.@.@.......\n        0x0010:  7f00 0001 2bcb e416 002f fe42 7155 0000  ....+..../.BqU..\n        0x0020:  0001 0000 8100 0000 0400 0000 0000 0007  ................\n        0x0030:  0000 0000 0000 0000 0000 0115 0000 0000  ................\n        0x0040:  7661 6c                                  val\n```\n\nNotice that there are two GET request packets with \"key\" payload.\n\nNow start `memcached-ebpf-proxy-cache`:\n\n```sh\nRUST_LOG=debug cargo xtask run  -- --iface lo\n```\n\nThe first `GET` should trigger an `update_cache` while the second `GET` should lead\nto a cache hit and trigger `write_reply`. Let's see if we can reproduce this behaviour.\n\nNow run `cargo test` and inspect the traffic again.\n\nRunning the test should yield a similar packet dump to the following packet dump:\n\n```\ntcpdump: verbose output suppressed, use -v[v]... for full protocol decode\nlistening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes\n21:25:21.492337 IP 127.0.0.1.51924 \u003e 127.0.0.1.11211: UDP, length 32\n        0x0000:  4500 003c 92c7 4000 4011 a9e7 7f00 0001  E..\u003c..@.@.......\n        0x0010:  7f00 0001 cad4 2bcb 0028 fe3b 8dbe 0000  ......+..(.;....\n        0x0020:  0001 0000 800b 0000 0000 0000 0000 0000  ................\n        0x0030:  0000 0000 0000 0000 0000 0000            ............\n21:25:21.492539 IP 127.0.0.1.11211 \u003e 127.0.0.1.51924: UDP, length 38\n        0x0000:  4500 0042 a004 4000 4011 9ca4 7f00 0001  E..B..@.@.......\n        0x0010:  7f00 0001 2bcb cad4 002e fe41 8dbe 0000  ....+......A....\n        0x0020:  0001 0000 810b 0000 0000 0000 0000 0006  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 312e 362e  ............1.6.\n        0x0040:  3134                                     14\n21:25:21.492749 IP 127.0.0.1.51924 \u003e 127.0.0.1.11211: UDP, length 35\n        0x0000:  4500 003f 92c8 4000 4011 a9e3 7f00 0001  E..?..@.@.......\n        0x0010:  7f00 0001 cad4 2bcb 002b fe3e 8dbf 0000  ......+..+.\u003e....\n        0x0020:  0001 0000 800c 0003 0000 0000 0000 0003  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 6b65 79    ............key\n21:25:21.492874 IP 127.0.0.1.11211 \u003e 127.0.0.1.51924: UDP, length 42\n        0x0000:  4500 0046 a005 4000 4011 9c9f 7f00 0001  E..F..@.@.......\n        0x0010:  7f00 0001 2bcb cad4 0032 fe45 8dbf 0000  ....+....2.E....\n        0x0020:  0001 0000 810c 0003 0400 0000 0000 000a  ................\n        0x0030:  0000 0000 0000 0000 0000 0117 0000 0000  ................\n        0x0040:  6b65 7976 616c                           keyval\n21:25:21.493021 IP 127.0.0.1.51924 \u003e 127.0.0.1.11211: UDP, length 32\n        0x0000:  4500 003c 92c9 4000 4011 a9e5 7f00 0001  E..\u003c..@.@.......\n        0x0010:  7f00 0001 cad4 2bcb 0028 fe3b 8dc0 0000  ......+..(.;....\n        0x0020:  0001 0000 800b 0000 0000 0000 0000 0000  ................\n        0x0030:  0000 0000 0000 0000 0000 0000            ............\n21:25:21.493178 IP 127.0.0.1.11211 \u003e 127.0.0.1.51924: UDP, length 38\n        0x0000:  4500 0042 a006 4000 4011 9ca2 7f00 0001  E..B..@.@.......\n        0x0010:  7f00 0001 2bcb cad4 002e fe41 8dc0 0000  ....+......A....\n        0x0020:  0001 0000 810b 0000 0000 0000 0000 0006  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 312e 362e  ............1.6.\n        0x0040:  3134                                     14\n21:25:21.493441 IP 127.0.0.1.11211 \u003e 127.0.0.1.51924: UDP, length 42\n        0x0000:  4500 0046 92ca 4000 4011 a9da 7f00 0001  E..F..@.@.......\n        0x0010:  7f00 0001 2bcb cad4 0032 0000 8dc1 0000  ....+....2......\n        0x0020:  0001 0000 810c 0003 0400 0000 0000 000a  ................\n        0x0030:  0000 0000 0000 0000 0000 0000 0000 0000  ................\n        0x0040:  6b65 7976 616c                           keyval\n```\n\n**Notice that there is only one GET request and two GETK responses. This is our proof that the last\nrequest was completely handled by the eBPF layer.**\n\nYou might also notice that we are getting both key and value in the reponse. That's because we\nmodify GET requests to GETK requests. This is important because we need the key in the reponse\npacket in order to be able to update the cache.\n\n## Benchmarking\n\nNow a word of honesty: this doesn't actually lead to a speedup as we expected, even though we are\nable to reduce the number of packets. Actually, we notice an overhead when we run `memcached-ebpf-proxy-cache`.\n\nThis project was originally inspired by the paper: [\"BMC: Accelerating Memcached using Safe\nIn-kernel Caching and Pre-stack Processing\"](https://www.usenix.org/conference/nsdi21/presentation/ghigoff)\n\nThe original paper used the memcached ASCII protocol. Also it made some enhancements to memcached to\nbe able to utilize [`SO_REUSEPORT`](https://lwn.net/Articles/542629/) to enhance multi-threaded performance.\n\nIn the project, I wanted to explore how far I could go using only eBPF and the stock `memcached` package\nthat's available in Ubuntu 22.04 or Arch Linux package repository. I also opted for the memcached binary\nprotocol since that's the one that's mostly used in production. So any lack of performance increase is solely\non this particular implementation. A faithful implementation of the paper should definitely be able to achieve\nthe published speedup.\n\nThe original paper authors also provided their own implementation here: \u003chttps://github.com/Orange-OpenSource/bmc-cache/\u003e\n\nNow back to our benchmarks. In order the run the benchmarks checkout to the `feat/memcached-bench` branch.\n\n```sh\ngit checkout feat/memcached-bench\n```\n\nNext compare the performance with and without `memcached-ebpf-proxy-cache`.\n\nFirst run `memcached-bench` while `memcached-ebpf-proxy-cache` is not running with the following command:\n\n```sh\ncargo run -p memcached-bench\n```\n\nNext run this command while `memcached-ebpf-proxy-cache` is running.\n\n```sh\nRUST_LOG=debug cargo xtask run  -- --iface lo\n\n# in a different terminal\ncargo run -p memcached-bench\n```\n\nDoing this yields the following results on my machine (HP Pavilion x360 Convertible 14-ba0xx running Endeavour OS):\n\n```text\n# without memcached-ebpf-proxy-cache\n100.00% |█████████████████████████████████████████████████████████████████▏| 91.00/91.00 [00:00:00] (11.58 it/s)\nTime spent in SET loop: 7.856820009s\n100.00% |███████████████████████████████████████████████████████████████▏| 10.00K/10.00K [00:00:00] (8.20K it/s)\nTime spent in GET loop: 1.219417s\n\n# with memcached-ebpf-proxy-cache\n100.00% |█████████████████████████████████████████████████████████████████▏| 91.00/91.00 [00:00:00] (11.56 it/s)\nTime spent in SET loop: 7.874415797s\n100.00% |███████████████████████████████████████████████████████████████▏| 10.00K/10.00K [00:00:00] (5.37K it/s)\nTime spent in GET loop: 1.861354611s\n```\n\nThere's a slight decrease in performance instead of the increase in performance we were expecting.\n\nThere can be a couple of reasons for this:\n\n- The binary protocol may be inherently much more efficient than the ASCII protocol, where the overhead due to\n  the networking stack doesn't matter. Rather the `memcpy()` in the eBPF layer might be incurring an overhead.\n- Lack of proper [`bpf_spin_lock`](https://docs.kernel.org/bpf/graph_ds_impl.html#id3) support in aya-rs - [aya-rs\n  currently lacks support for bpf_spin_lock](https://github.com/aya-rs/aya/issues/857) due to this\n  [issue](https://github.com/aya-rs/aya/issues/351) as of 22-09-2024. So I improvised and implemented my\n  own spinlock using atomic intrinsic [`atomic_xchg_seqcst`](https://doc.rust-lang.org/core/intrinsics/fn.atomic_xchg_seqcst.html).\n  My implementation may not be as efficient as the real thing.\n- We are sending back both the KEY and VAL in GET requests. This can incur a data transfer overhead.\n  (Although, we are still on localhost.)\n\nRegardless this was a fun learning exercise. I learned a lot about:\n\n- XDP and TC packet filtering and processing\n- Packet unpacking and restructuring at different protocol levels\n- Tail calls\n- Different map types: BPF Map type Array, Program Array, Per CPU array etc.\n- Atomic intrinsics\n- Satisfying the eBPF verifier with proper loop range and memory acccess bounds\n\nI have more or less achieved what I wanted to - which was to understand how to write eBPF programs. So I'll stop\nhere for now. Regardless, all contributions to improve performance are very much welcome.\n\n## License\n\nThis repository is licensed under the MIT License. See [LICENSE](./LICENSE) for more details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farindas%2Fmemcached-ebpf-proxy-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farindas%2Fmemcached-ebpf-proxy-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farindas%2Fmemcached-ebpf-proxy-cache/lists"}