{"id":13507525,"url":"https://github.com/jr0senblum/jc","last_synced_at":"2026-02-18T22:02:07.101Z","repository":{"id":57509051,"uuid":"41772405","full_name":"jr0senblum/jc","owner":"jr0senblum","description":"Erlang, in-memory distributable cache","archived":false,"fork":false,"pushed_at":"2025-06-17T16:50:04.000Z","size":2615,"stargazers_count":26,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-12-27T05:05:32.774Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jr0senblum.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}},"created_at":"2015-09-02T01:26:45.000Z","updated_at":"2025-12-09T19:11:40.000Z","dependencies_parsed_at":"2022-08-30T07:10:12.371Z","dependency_job_id":null,"html_url":"https://github.com/jr0senblum/jc","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jr0senblum/jc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jr0senblum%2Fjc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jr0senblum%2Fjc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jr0senblum%2Fjc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jr0senblum%2Fjc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jr0senblum","download_url":"https://codeload.github.com/jr0senblum/jc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jr0senblum%2Fjc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29596329,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T20:59:56.587Z","status":"ssl_error","status_checked_at":"2026-02-18T20:58:41.434Z","response_time":162,"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":[],"created_at":"2024-08-01T02:00:35.701Z","updated_at":"2026-02-18T22:02:07.083Z","avatar_url":"https://github.com/jr0senblum.png","language":"Erlang","funding_links":[],"categories":["Caching"],"sub_categories":[],"readme":"JC  \n====\n## Erlang, Distributable, In-Memory Cache\n\n### Featuring: Pub/Sub, JSON-query, and mechanisms to support consistency without transactoins.\n\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/jr0senblum/jc)\n[![Build Status](https://travis-ci.org/jr0senblum/jc.svg?branch=master)](https://travis-ci.org/jr0senblum/jc)\n[![hex.pm version](https://img.shields.io/hexpm/v/jc.svg)](https://hex.pm/packages/jc)\n\n\n### Features\n* Cache entries are Map, Key, Value, [TTL], [Sequence]\n  * Maps represent a name-space for Keys - similar to the notion\n    of 'bucket'\n    in other caching systems\n  * Maps, Keys and Values can be any Erlang term\n  * TTL is time-to-live in seconds\n* Consistency assist\n  * Client-Side Sequence Number: An alternative API allows for a sequence-number\n    parameter on the put/x, evict/x, match/x and remove/x operations. Operations\n    whose sequence number is lower than the current, per-map max are disallowed\n    thereby ensuring, for example, that an old delete that shows up after a newer update\n    does not inappropriately evict the newer update.\n  * Node of Responsibility: A key-specific node can be identified for destructive\n    operations (put, evict, etc.) thereby preserving eventual consistency without transactions. \n      * jc_store:locus/2 takes a Key and a list of nodes and returns the node of \n    responsibility or the given key.\n      * jc_bridge accepts a message {From, {locus, Key}} and returns the node of\n      responsibility. Because jc\\_bridge knows which nodes are available, the client\n      is relieved from keeping track of up-nodes, which is necessary to\n      caclulate the correct node of responsiblility. For example:\n  \n ~~~~ Erlang\n    {jc_bridge, Any_Cache_Node} ! {self(), {locus, Key}},\n    NOR = receive\n        {error, _} -\u003e error\n              Node -\u003e Node\n    after\n        1000 -\u003e error\n    end,\n    {jc_bridge, NOR} ! {self, {put, benchtest, 10203, \"{\\\"Key\\\":\\\"Value\\\"}\n ~~~~  \n    Because data is everywhere, a lookup will always find a key irrespective of \n    the node of responsibility. The way to best use this is to configure jc_sequence\n    to not be a singleton and then use the Node of Responsibility feature to chose\n    the node to do the inserts/deletes using the jc_s API.\n    \n* JSON query support\n  * Query by JSON: When Values are JSON, evict_match/2,\n    evict_all_match/1 and values_match/2 will search or evict\n    keys whose JSON value, at a location specificed by a java-style, \n    dot-path string, equals the given value. That is,\n    jc:values_match(bed, \"id.type=3\") would return all values, in the given\n    map (bed), where that value was a JSON object, id, with a \"type\":3\n    at its top-level.\n  * Ad-hoc, index support: In order to support faster\n    operations, (2-3 orders of magnitude), each map can have up to four,\n    dot-path, strings configured (or added at run-time) for which jc will\n    provide index support.\n   * Auto index recognition - Frequently used JSON querries will be\n     automatically detected and indexing initiated.\n* User controlled eviction\n  * Map-level TTL: A job runs at configured intervals and removes\n  items whose create-date is older than a map-specific, configured \n  number of seconds.\n  * Item-level TTL: PUTS can include a TTL which defines when the\n  item should be evicted. Used for shorter TTLs, as an exception\n  to a Map-level TTL, or when more precision is required than that\n  offered by the Map-level TTL.\n* Pub/Sub \n  * Clients can subscribe to Map events for a specific key or\n  for any key,  and for write, delete or either operations\n  * Clients can create and subscribe to arbitrary 'topics' and \n  broadcast arbitrary messages under those topic names\n  * Clients can subscribe to node-up and node-down events \n* Fine-grained logging via Lager\n\n\n\n### Cache Functions (jc)\n* Create\n  * put(Map, Key, Value, [TTLSecs]) -\u003e {ok, Key} | {error, badarg}\n  * put_all(Map, [{K,V},{K,V},...], [TTLSecs]) -\u003e {ok, CountOfSuccessfulPuts} |\n                                                  {error, badarg}\n* Delete\n  * evict(Map, Key) -\u003e ok\n  * evict_all_match(Criteria = \"Json.Path.Match=Value\") -\u003e ok\n  * evict_map_since(Map, Age_In_Secs) -\u003e ok\n  * evict_match(Map, Criteria = \"Json.Path.Match=Value\") -\u003e ok\n  * remove_items(Map, Keys) -\u003e {ok, [{K, V}, ...]} for each {K, V} deleted.\n* Retrieve\n  * get(Map, Key) -\u003e {ok, Value} | miss\n  * get_all(Map, [K1, K2, ...]) -\u003e {ok, {Found=[{K1,V1},...], Misses=[K2,...]}}\n  * key_set(Map) -\u003e {ok, [K1, K2, ...]} for each Key in the Map\n  * values(Map) -\u003e {ok, [V1, V2, ...]} for each Value in the Map\n  * values_match(Map, Criteria=\"JSon.Path.Match=Value\") -\u003e\n                                                  {ok, [{K1,V1}, {K2,V2}, ...]}\n* Flush\n  * clear(Map) -\u003e ok\n  * flush() -\u003e ok\n  * flush(silent) -\u003e ok, Does not send alerts to subscribers\n\n* Predicates\n  * contains_key(Map, Key) -\u003e true | false.\n  * map_exists(Map) -\u003e true | false.\n\n* Meta\n  * cache_nodes() -\u003e {{active, [Node1,... ]}, {configured, [Node1,... ]}}\n  * cache_size() -\u003e {size, [{TableName, RecordCnt, Words}],...}\n  * map_size(Map) -\u003e {records, Count}\n  * maps() -\u003e {maps, [Map1, Map2,...]}\n  * up() -\u003e {uptime, [{up_at, String},{now, String},\n                      {up_time, {D, {H, M, S}}}]\n\n\n### Consistency Support Functions (jc_s)\nIdentical to the Create and Evict family of functions of the jc module\n(see above), except:\n\n* An additional sequence parameter, which is expected to be a monotonically\n  incresing integer (with respect to a given Map), used to disalow\n  \"out of sequence\" operations\n* Functions return {error, out_of_seq} if out of sequence operation is attempted\n  * evict(Map, Key, Seq)\n  * evict_all_match(Criteria = \"Json.Path.Match=Value\", Seq) \n  * evict_match(Map, Criteria = \"Json.Path.Match=Value\", Seq)\n  * put(Map, Key, Value, [TTLSecs], Seq) \n  * put_all(Map, [{K,V},{K,V},...], [TTLSecs], Seq)\n  * remove_items(Map, Keys, Seq)\n* Meta functions\n  * sequence() -\u003e [{Map, HighestNnumber},... ]\n  * sequence(Map) -\u003e HightestNumber\n\n\n\n### Eviction Manager Functions (jc_eviction_manager)\n* set_max_ttl(Map, Secs) -\u003e ok | {error, badarg}\n* get_max_ttls() -\u003e [{Map, Secs}, ...]\n\n\n### Pub/Sub Functions (jc_psub)\n* map_subscribe(Pid, Map, Key|any, write|delete|any) -\u003e ok | {error, badarg}\n* map_unsubscribe(Pid, Map, Key|any, write|delete|any) -\u003e ok | {error, badarg}\n  * client receives\n  \n    `{map_event, {Map, Key, delete}}`\n    or\n    `{map_event, {Map, key, write, Value}`\n* topic_subscribe(Pid, Topic, Value) -\u003e ok | {error, badarg}\n* topic_unsubscribe(Pid, Topic, Value) -\u003e ok | {error, badarg}\n    * client receives: {topic_event, {Topic, Value}}\n* topic_event(Topic, Value) -\u003e ok\n  * Broadcasts Value to all\n  subscribers of Topic\n* topic_subscribe(Pid, jc_node_events, any) -\u003e ok \n  * subscribes the user to node up and node down events:\n  \n  `{jc_node_events, {nodedown, DownedNode, [ActiveNodes],[ConfiguredNodes]}}`\n  \n  `{jc_node_events, {nodeup, UppedNode, [ActiveNodes],[ConfiguredNodes]}}`\n\n\n### Indexing Functions (jc_store)\n  * start_indexing(Map, Path={bed,\"menu.id\"}) -\u003e ok |\n                                               {error, no_indexes_available} |\n\t\t\t\t\t\t\t       {error, Term}\n  * stop_indexing(Map, Path={bed,\"menu.id\"}) -\u003e ok\n  * indexes(Map) -\u003e {indexes, [{{Map, Path}, Position},...]} for all indexes\n                                                  of given Map\n  * indexes() -\u003e {indexes, [{{Map, Path}, Position},...]} for all indexes\n\n\n### Interoperability: Bridge (jc_bridge)\n * All functions from the jc, jc_s, jc_eviction_manager, jc_psub\n and jc_store are supported and are of the form:\n \n   `{From, {Fn, P1, P2,...}}`\n   \n    for each paramater, as in \n    \n    `jc_bridge ! {Pid, {put, Map, Key, Value}}`\n    \n* Additionally, \n  `{From, locus, Key}} -\u003e node()` Calls jc_store:locus/2 with the list of active\n  cache nodes and returns the node of record.\n\n\n  {From, {node_topic_sub}} -\u003e ok | {error, badarg}, \n  client will recieve:\n   \n   `{jc_node_events, {nodedown, DownedNode, [ActiveNodes],[ConfiguredNodes]}}`\n   \n    or\n\n    `{jc_node_events, {nodeup, UppedNode, [ActiveNodes],[ConfiguredNodes]}}`\n\n   `{From, {node_topic_unsub}} -\u003e ok`.\n\n\n### Configuration\n* Application configuration is in sys.config which is heavily\n  commented\n* Cookie, node-name and auto-restart of VM controlled by vm.args\n\n\n### Application Modules\n* jc_cluster\n  * Simple, mnesia-based, cluster creation and management\n* jc, jc_s, jc_store, jc_eviction_manager\n  * Caching operations, Key-specific and Map-level TTLs\n* jc_sequence\n  * [optionally] Singleton service enforcing strictly monotonic sequence \n  numbers on jc_s operations\n* jc_analyzer\n  * Analysis and indexing inititation of JSON query strings\n* jc_psub: \n  * Pub / Sub of cache write and delete events\n  * On-demand, ad-hoc topic events\n  * Predefined, *jc_node_events* topic provides subscribers\n  node-up and node-down notifications\n* jc_bridge\n  * Server that acts as a proxy between an external process and\n  jc functionality\n* jc_netsplit\n  * Looks for evidence of node dis/apperation and implements a recovery\n    strategy\n\n### Net Split/Join Strategy\nMnesia does not merge on its own when a node joins (returns) to a mesh of nodes.\nThere are two situations where this is relevant:\n\n* j_cache nodes start in a disconnected state so more than one initiates a new\ncluster and then, subsequently, those nodes join into one cluster;\n* A node drops out of the cluster due to some network glitch and then rejoins.\n\nTo handle these situations, whenever a cluster is created by a Node (node@123.45.67, \nfor example), it creates a ClusterId - its Node name (node@123.45.67), for that cluster.\n\nGiven this ClusterId, we have the following strategy:\n\n1. _Cluster Creation_: creates an initial ClusterId;\n2. _Nodedown_: If the Node that created the cluster dissapears, a surviving Node changes the\n   ClusterId such that ClusterId is now this new Node's name. In the case of a\n   disconnected newtwork, one of the islands will have the original ClusterId Node \n   dissapear, and it will create a new one as described.\n3. _Nodeup_ Whenever a Node appears, an arbitary Node ensures that any Nodes that report\n    a different ClusterId (different than the arbitrary Node's ClusterId) are killed to be\n    restarted by the hearbeat application. If any Nodes required restarting, the entire \n    cache is flushed or not per policy in config.sys.\n\n### Build Instructions\n* Ensure that Erlang 17 or higher is installed\n* Get the Source Code from Stash\n\n   `[root@db01] git clone https://github.com/jr0senblum/jc.git`\n\n * Edit the sys.config and vm.args files in ./config\n    * vm.args: Indicate the correct node names and cookie\n    * sys.config: Adjust prarameters as neccesary.\n\n     `[root@db01] ./rebar3 release`\n    \n     or\n\n     `[root@db01] ./rebar3 as prod release`\n   \t\n\n### Documentation\n\n   `[root@dbo1] ./rebar3 edoc`\n\n## Component and Sequence Diagrams\n\n### Components\n\n\n![](https://cloud.githubusercontent.com/assets/2043491/10463722/1aff2856-71b4-11e5-8e0a-5fcbee0c3ea3.png)\n\n![](https://cloud.githubusercontent.com/assets/2043491/10463734/2446743c-71b4-11e5-8c6a-6de2da844fbf.png)\n\n![](https://cloud.githubusercontent.com/assets/2043491/10463736/290403f4-71b4-11e5-8b94-bd7273d4c7fa.png)\n\n\n### Sequence Diagrams\n![](https://cloud.githubusercontent.com/assets/2043491/10463819/9c59b7f4-71b4-11e5-9db9-a82fa762240c.png)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjr0senblum%2Fjc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjr0senblum%2Fjc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjr0senblum%2Fjc/lists"}