{"id":16746993,"url":"https://github.com/selfup/smache","last_synced_at":"2025-08-11T22:41:07.093Z","repository":{"id":71266611,"uuid":"128011899","full_name":"selfup/smache","owner":"selfup","description":"Distributed non-replicating immutable cache that can handle dynamic auto scaling.","archived":false,"fork":false,"pushed_at":"2025-02-18T05:21:03.000Z","size":329,"stargazers_count":7,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T14:13:22.214Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/selfup.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":"2018-04-04T05:32:18.000Z","updated_at":"2025-02-15T01:01:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"8dc834ae-1483-42e1-92a0-4bce00aef34f","html_url":"https://github.com/selfup/smache","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/selfup/smache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selfup%2Fsmache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selfup%2Fsmache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selfup%2Fsmache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selfup%2Fsmache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/selfup","download_url":"https://codeload.github.com/selfup/smache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selfup%2Fsmache/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269969821,"owners_count":24505437,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2024-10-13T02:08:50.471Z","updated_at":"2025-08-11T22:41:07.005Z","avatar_url":"https://github.com/selfup.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Smache\n\nCache that can get Smashed :tada: _warning this is alpha stage software_\n\nDistributed - Scalable - Serialized - Immutable - Fault Tolerant - Self Sharding - Key Value Cache :rocket:\n\n1. Provides a JSON API that can handle concurrent requests (Phoenix) but serializes all writes to memory\n1. When nodes are behind a load balancer every node you add can become connected to each other (distributed)\n1. Distribute your cache by adding machines (shards) and they automatically figure out where to grab data\n1. RAM IO and all cache is handled using [ETS](https://elixir-lang.org/getting-started/mix-otp/ets.html)\n\n_Suprisingly performant_ :smile:\n\n## Release Repo\n\nOver at GitLab the release repo lives!\n\nRepo: https://gitlab.com/selfup/smache\n\nDocker Registry for Smache: `registry.gitlab.com`\n\nImage tag: `registry.gitlab.com/selfup/smache:latest`\n\n## Purpose\n\nHigh frequency short term cache storage. If this does not fit your bill there are caveats!\n\nCommon use cases would be something like location data for realtime applications (Uber/Lyft/etc...)\n\n### Example JSON API\n\n### Healthcheck\n\n**GET** `/`\n\nReturns an empty object in json: `{}`\n\n### Get Key\n\n**GET** `/api`\n\nParams: `?key=some_key`\n\nReturns (for now) key/data/node\n\n```json\n{\n  \"key\": \"some_key\",\n  \"data\": {\n    \"color\": \"blue\"\n  },\n  \"node\": \"smache@localhost\"\n}\n```\n\n### Post Data\n\n**POST** `/api`\n\n_key should be numbers but can be any string_\n\n_data key must always be an object_\n\n```json\n{\n  \"key\": \"something\",\n  \"data\": {\n    \"msg\": \"hello\"\n  }\n}\n```\n\n## Why was this built?\n\nAs an expirement. Turns out it works.\n\nHere's why developement has continued:\n\n1. Each node becomes it's own shard. Automagically\n1. Very cheap to run idle, auto scaling takes care of the rest like stateless apis\n1. Some of the data has to rebuild but that's not a big deal in it's use case\n1. Location data is only valid for a certain amount of time\n1. This was initially built as a backpressure mitigation tool for High IO NoSQL data\n1. It turns out it's really fast\n\n## Built for Load Balancing\n\nLoad Balance your cluster of cache nodes (static or dynamic)\n\n1.  Static clusters (say you stick with 20 forever)\n\n        a. Will never lose references to existing data (unless rebooted, or the node crashes)\n        b. Will be easier to maintain\n\n2.  Dynamic clusters (auto scaling)\n\n        a. Will lose a percentage of data per shard on expansion/shrinkage\n        b. Data rebuilds as fast as it did the first time\n        c. Best price to performance ratio\n\n## TODOS\n\n1. Figure out cache invalidation strategies\n1. Wipe old data from nodes that have a new respective shard\n\n## Caveats\n\nStale data is still a thing for now. :sob:\n\nAll nodes are dependant on a node longname (`smache@internal_ip`) to be provided on bootup to connect to the network :rocket:\n\nNo TLS support for internal calls. Must be in a VPC or a managed cluster. Also Digital Ocean has sweet Private Networking features.\n\nTo auto shard at scale, all keys are turned into an integer if not already an integer :warning:\n\nAll nil/null keys are rejected with a 405 :boom:\n\nIf you plan to use integers and strings (as keys), please read the following:\n\nExamples:\n\n1. `1` -\u003e `1` (integer remains an integer)\n2. `\"1\"` -\u003e `1` (string that can be casted into integer)\n3. `\"aa\"` -\u003e `24_929` (string that needs to be encoded into an integer)\n4. `\"aaa\"` -\u003e `6_381_921` (string that needs to be encoded into an integer)\n5. `\"abcd\"` -\u003e `1_633_837_924` (string that needs to be encoded into an integer)\n\n_Simply put:_ to avoid collisions (int/char) check the range of a string to see how far up you can use a normal integer key.\n\nFor example here:\n\n1. 4 chars starts at 1.6+ billion\n1. 3 chars starts at 6.3+ million\n1. 2 chars starts at 24+ thousand\n\nUnless you are storing that much data, make sure to store strings of a certain length to ensure they do not colide with already stored integers (or vice versa) :pray:\n\nConsider using shas or ids only. Numeric ids are prefered!\n\n## Development\n\n**Deps**:\n\n1. Docker\n1. Elixir\n1. Bash\n1. macOS or Linux (Windows can work but is cumbersome)\n\n**On first boot**:\n\n`./scripts/services.sh`\n\n1. Generate Secret/Cookie\n1. Ensure deps are installed and compiled\n1. Build the image\n1. Run all 4 containers\n\nEssentially the _bootstrapping_ scripts :rocket:\n\n### Have two local nodes talk to eachother in dev\n\n```bash\n# in one shell\n\n./scripts/dev.sh 4000 smache@localhost localhost\n\n# in a second shell\n./scripts/dev.sh 4001 smache_two@localhost localhost\n```\n\nNow run the curl scripts (in a third shell):\n\n```bash\n./scripts/curl.post.sh 4000\n\n./scripts/curl.get.sh 4001\n```\n\n### How to have 4 nodes talk via docker\n\n```bash\n# boots a Load Balancer (haproxy) and 4 smache nodes\n./scripts/services.sh\n\n# posts 7 different keys and data\n./scripts/curl.post.sh 8080\n\n# gets all posted data\n./scripts/curl.get.sh 8080\n```\n\n### Performance (stress test)\n\nLinux / Ubuntu 18.04.1\n\n**Single node**\n\n_900 clients_\n\nControl (health check returning empty JSON object):\n\n```ocaml\nFinished 50000 requests\n\n--\u003e results:\n\n    Time taken for tests:   1.637 seconds\n    Requests per second:    30539.96 [#/sec] (mean)\n      50%     28 ms\n      95%     46 ms\n     100%     76 (longest request)\n```\n\n---\n\n**Single Node**\n\n_900 clients_\n\nPOST key/data return data in JSON:\n\n```ocaml\nFinished 50000 requests\n\n--\u003e results:\n\n    Time taken for tests:   2.136 seconds\n    Requests per second:    23411.13 [#/sec] (mean)\n      50%     37 ms\n      95%     62 ms\n     100%     94 (longest request)\n```\n\nGET key return key/data/node in JSON:\n\n```ocaml\nFinished 50000 requests\n\n--\u003e results:\n\n    Time taken for tests:   1.880 seconds\n    Requests per second:    26595.15 [#/sec] (mean)\n      50%     32 ms\n      95%     52 ms\n     100%     78 (longest request)\n```\n\n**Two Nodes (force distributed RPC calls)**\n\n_900 clients_\n\nPOST key/data return data in JSON:\n\n```ocaml\nFinished 50000 requests\n\n--\u003e results:\n\n    Time taken for tests:   2.845 seconds\n    Requests per second:    17576.51 [#/sec] (mean)\n      50%     50 ms\n      95%     77 ms\n     100%    118 (longest request)\n```\n\nGET key return key/data/node in JSON:\n\n```ocaml\nFinished 50000 requests\n\n--\u003e results:\n\n    Time taken for tests:   2.681 seconds\n    Requests per second:    18651.75 [#/sec] (mean)\n      50%     46 ms\n      95%     76 ms\n     100%    167 (longest request)\n```\n\n### Internal Performance\n\n```elixir\n1..4 |\u003e Enum.each(fn _ -\u003e Smache.Mitigator.bench() end)\n```\n\nResults:\n\n- 10k records written then read in: 18.593 ms\n- 10k records written then read in: 17.226 ms\n- 10k records written then read in: 15.206 ms\n- 10k records written then read in: 15.007 ms\n- 10k records written then read in: 39.919 ms\n- 10k records written then read in: 15.344 ms\n- 10k records written then read in: 16.827 ms\n- 10k records written then read in: 15.346 ms\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fselfup%2Fsmache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fselfup%2Fsmache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fselfup%2Fsmache/lists"}