{"id":18546434,"url":"https://github.com/adroll/mero","last_synced_at":"2025-10-26T17:18:15.403Z","repository":{"id":29838967,"uuid":"33383699","full_name":"AdRoll/mero","owner":"AdRoll","description":"scalable and lightweight OTP Erlang client for memcached","archived":false,"fork":false,"pushed_at":"2024-07-01T00:13:18.000Z","size":627,"stargazers_count":48,"open_issues_count":4,"forks_count":20,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-03-31T17:18:39.157Z","etag":null,"topics":["aws","cluster","distributed","erlang","hacktoberfest"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/mero","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AdRoll.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2015-04-03T21:30:29.000Z","updated_at":"2024-07-05T01:31:59.000Z","dependencies_parsed_at":"2024-01-23T13:24:50.980Z","dependency_job_id":"cee58200-1c1d-4348-b157-92a8d85d89fe","html_url":"https://github.com/AdRoll/mero","commit_stats":{"total_commits":168,"total_committers":20,"mean_commits":8.4,"dds":0.8154761904761905,"last_synced_commit":"5d991a4e2def8307fa259c44aef55ec343bdcb42"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdRoll%2Fmero","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdRoll%2Fmero/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdRoll%2Fmero/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdRoll%2Fmero/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AdRoll","download_url":"https://codeload.github.com/AdRoll/mero/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247767236,"owners_count":20992548,"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","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":["aws","cluster","distributed","erlang","hacktoberfest"],"created_at":"2024-11-06T20:24:57.562Z","updated_at":"2025-10-26T17:18:15.358Z","avatar_url":"https://github.com/AdRoll.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"Mero\n========\n\nMero is a scalable and lightweight OTP Erlang client for memcache. Mero allows\ninteraction with different clusters, specifying different number of pools per\nserver and sharding algorithms per cluster.\n\nMero achieves high-performance in a number of ways. Mero does not use an Erlang\nprocess per socket. Each pool worker adjusts the number of available sockets\nbased on demand and sockets are shared to the point of usage, avoiding copying\nof terms through the system, as is present in the per-process design common in\nother pooling libraries. All pools are registered with a local name and its\nworkers are created on startup, removing pool selection bottlenecks and worker\ncreation latency.\n\nIf a connection to the memcached server fails there is a mechanism to delay\nconnection retries. All the connections are renewed every time interval.\n\nThe storage module is configurable so you can use different protocols or even\nuse a storage different than memcache.\n\nIt includes a callback that will be called to notify of specific error events.\nThese events have the form of:\n\n```erlang\n{Id :: list(atoms),\n  Args :: list([{Key :: cluster_name | host | port | error_reason,\n                 Value :: term()}])\n}\n```\n\nExample Ids are:\n\n - `[socket, connect, ok]`\n - `[socket, connect, error]`\n - `[socket, send, error]`\n - `[socket, rcv, error]`\n - `[socket, controlling_process, error]`\n\nConfiguration\n=============\n\nPlease consult `mero.app.src` to see all the available options. The sharding\nalgorithms available are `shard_phash2` and `shard_crc32`.\n\n```erlang\n  [{cluster_a,\n     [{servers, [{\"server1\", 11211},\n                 {\"server2\", 11211},\n                 {\"server3\", 11211},\n                 {\"server4\", 11211},\n                 {\"server5\", 11211},\n                 {\"server6\", 11211},\n                 {\"server7\", 11211},\n                 {\"server8\", 11211}]},\n      {sharding_algorithm, {mero, shard_phash2}}, %% Module and function\n      {workers_per_shard, 3},                         %% Number of pools that each server will have\n      {pool_worker_module, mero_wrk_tcp_txt}]\n  },\n\n  {cluster_b,\n     [{servers, [{\"localhost\", 11211}]},\n      {sharding_algorithm, {mero, shard_crc32}},\n      {workers_per_shard, 1},\n      {pool_worker_module, mero_wrk_tcp_txt}]\n  },\n  ...\n]\n\n```\n\nCluster Auto Discovery\n======================\n\nConfiguration can also be implemented to support auto discovery of the cluster as opposed to hardcoding nodes.\nProvide the configuration endpoint ([AWS Reference](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/AutoDiscovery.html)) and port as in the following example.\n\n```erlang\n    [{cluster_b,\n        [{servers, {elasticache, \"ConfigEndpointHostname.com\", PortNumber}},\n             {sharding_algorithm, {mero, shard_crc32}},\n             {workers_per_shard, 1},\n             {pool_worker_module, mero_wrk_tcp_binary}]\n    },\n    ...\n]\n\n```\n\nIf instead of using ElastiCache a custom auto discovery is needed you can provide your own function (specified as a MFA) that returns `{ok, list({Host :: inet:hostname(), Port :: inet:port_number()})}` to be used. Example:\n```erlang\n    [{cluster_b,\n        [{servers, {mfa, {my_module, my_function, []}}},\n             {sharding_algorithm, {mero, shard_crc32}},\n             {workers_per_shard, 1},\n             {pool_worker_module, mero_wrk_tcp_binary}]\n    },\n    ...\n]\n\n```\n\n```erlang\n-module(my_module).\n\n-export([my_function/0]).\n\nmy_function() -\u003e\n  {ok, [{\"my_memcached_server\", 11211}]}.\n\n``` \n\nElastiCache Multiple clusters Auto Discovery\n============================================\n\nWe also support the setup of multiple physical clusters with autodiscovery assigned to the same logical cluster.\nIt could be the case in which some of these physical clusters perform better than others, in which you can use a third argument called the ClusterSpeedFactor which has to be a small integer. The default `ClusterSpeedFactor` is `1`.\n\nIf an alternate cluster is 2 times faster than the fist one -it can have twice as many cpus or memory-, you can set it up with a `ClusterSpeedFactor` of `2`. This will create 2 times more workers for that \"faster cluster\", which in practice will send twice as many connections \u0026 requests to that cluster than to the other weaker clusters.\n\n```erlang\n    [{cluster_c,\n        [{servers,\n           {elasticache,\n            [{\"ConfigEndpointHostname.com\", 11211},\n             {\"ConfigEndpointHostnameAltThatPerforms2TimesBetter.com\", 11211, 2},\n             {\"ConfigEndpointHostnameAltThatPerforms3TimesBetter.com\", 11211, 3}]\n            }},\n         {sharding_algorithm, {mero, shard_crc32}},\n         {workers_per_shard, 1},\n         {pool_worker_module, mero_wrk_tcp_binary}]}]\n```\n\nAuto Discovery Changes\n======================\n\nMero will also monitor elasticache or your custom provided MFA for changes in the configuration. It will poll the config provider according to the values of `conf_monitor_min_sleep` and `conf_monitor_max_sleep` in the configuration. The polling will run at a random interval that will always be between those two values (in milliseconds).\n\nIf a cluster configuration change is found after a poll, the connections to the servers of that cluster will be restablished. This is done using OTP supervision principles: Mero maintains a supervision tree per cluster, which is stopped and restarted if its configuration changes.\n\n\nUsing Mero\n===============\nMero is a regular OTP application, managed with [rebar3](http://rebar3.org/). Therefore you can do stuff like...\n\n```shell\nrebar3 do compile, xref, eunit, ct\n```\n\n...or...\n\n```shell\nrebar3 test\n```\n\nThere are three ways to start this application:\n\n### From an erlang shell\n```erlang\n$rebar3 shell\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key\"\u003e\u003e).\n{ok,1}\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key\"\u003e\u003e).\n{ok,2}\n\n\u003e mero:get(default, \u003c\u003c\"key\"\u003e\u003e).\n{\u003c\u003c\"key\"\u003e\u003e,\u003c\u003c\"2\"\u003e\u003e}\n\n\u003e mero:set(default, \u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"5\"\u003e\u003e, 3600, 5000).\nok\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key\"\u003e\u003e).\n{ok,6}\n\n\u003e mero:set(default, \u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"50\"\u003e\u003e, 3600, 5000).\nok\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key\"\u003e\u003e).\n{ok,51}\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key2\"\u003e\u003e).\n{ok,1}\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key3\"\u003e\u003e).\n{ok,1}\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key4\"\u003e\u003e).\n{ok,1}\n\n\u003e mero:mget(default, [\u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"key2\"\u003e\u003e, \u003c\u003c\"key3\"\u003e\u003e, \u003c\u003c\"key4\"\u003e\u003e], 5000).\n[{\u003c\u003c\"key\"\u003e\u003e,\u003c\u003c\"51\"\u003e\u003e},\n {\u003c\u003c\"key2\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e},\n {\u003c\u003c\"key3\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e},\n {\u003c\u003c\"key4\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e}]\n\n\u003e mero:set(default, \u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"key\"\u003e\u003e, 3600, 5000).\nok\n\n\u003e mero:set(default, \u003c\u003c\"key2\"\u003e\u003e, \u003c\u003c\"key2\"\u003e\u003e, 3600, 5000).\nok\n\n\u003e mero:increment_counter(default, \u003c\u003c\"key\"\u003e\u003e).\n{error,incr_decr_on_non_numeric_value}\n=ERROR REPORT==== 3-Apr-2015::13:57:40 ===\n    error: memcached_request_failed\n    client: {client,#Port\u003c0.3562\u003e,undefined,\n                    {mero,stat_event_callback,\n                              [{cluster_name,default},\n                               {host,\"localhost\"},\n                               {port,11211}]}}\n    cmd: {5,{\u003c\u003c\"key\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e,\u003c\u003c\"86400\"\u003e\u003e}}\n    reason: incr_decr_on_non_numeric_value\n\n\u003e mero:mget(default, [\u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"key2\"\u003e\u003e, \u003c\u003c\"key3\"\u003e\u003e, \u003c\u003c\"key4\"\u003e\u003e], 5000).\n[{\u003c\u003c\"key\"\u003e\u003e,\u003c\u003c\"key\"\u003e\u003e},\n {\u003c\u003c\"key2\"\u003e\u003e,\u003c\u003c\"key2\"\u003e\u003e},\n {\u003c\u003c\"key3\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e},\n {\u003c\u003c\"key4\"\u003e\u003e,\u003c\u003c\"1\"\u003e\u003e}]\n\n\u003e mero:flush_all(default).\n[{default,ok}]\n\n\n\u003e mero:mget(default, [\u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"key2\"\u003e\u003e, \u003c\u003c\"key3\"\u003e\u003e, \u003c\u003c\"key4\"\u003e\u003e], 5000).\n[{\u003c\u003c\"key\"\u003e\u003e,undefined},\n {\u003c\u003c\"key2\"\u003e\u003e,undefined},\n {\u003c\u003c\"key3\"\u003e\u003e,undefined},\n {\u003c\u003c\"key4\"\u003e\u003e,undefined}]\n\n\u003e mero:add(default, \u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"value\"\u003e\u003e, 1000, 5000).\nok\n\n\u003e mero:add(default, \u003c\u003c\"key\"\u003e\u003e, \u003c\u003c\"value\"\u003e\u003e, 1000, 5000).\n{error,already_exists}\n\n=ERROR REPORT==== 3-Apr-2015::14:03:25 ===\n    error: memcached_request_failed\n    client: {client,#Port\u003c0.3558\u003e,undefined,\n                    {mero,stat_event_callback,\n                              [{cluster_name,default},\n                               {host,\"localhost\"},\n                               {port,11211}]}}\n    cmd: {2,{\u003c\u003c\"key\"\u003e\u003e,\u003c\u003c\"value\"\u003e\u003e,\u003c\u003c\"1000\"\u003e\u003e}}\n    reason: already_exists\n\n\u003e {mero:madd(default, [{\u003c\u003c\"foo\"\u003e\u003e, \u003c\u003c\"bar\"\u003e\u003e, 1000},\n                       {\u003c\u003c\"bar\"\u003e\u003e, \u003c\u003c\"foo\"\u003e\u003e, 1000},\n                       {\u003c\u003c\"foo\"\u003e\u003e, \u003c\u003c\"baz\"\u003e\u003e, 1000}], 5000),\n   mero:mget(default, [\u003c\u003c\"foo\"\u003e\u003e, \u003c\u003c\"bar\"\u003e\u003e], 5000)}.\n{[ok,ok,{error,already_exists}],\n [{\u003c\u003c\"bar\"\u003e\u003e,\u003c\u003c\"foo\"\u003e\u003e},{\u003c\u003c\"foo\"\u003e\u003e,\u003c\u003c\"bar\"\u003e\u003e}]}\n\n\u003e mero:mcas(default, [{\u003c\u003c\"xyzzy\"\u003e\u003e, \u003c\u003c\"bar\"\u003e\u003e, 0, 360391},\n                      {\u003c\u003c\"qwer\"\u003e\u003e, \u003c\u003c\"asdfsdf\"\u003e\u003e, 0, 360390}], 5000).\n[ok,{error,already_exists}]\n```\n\n\n### Inside a Node\nSet the configuration in the mero.app.src file and start the application inside your OTP node as a regular OTP app.\n\n### As an OTP included application\nPass the ClusterConfiguration as a parameter to the supervisor of the application.\n\n```erlang\nmero_sup:start_link([{default,[{servers,[{\"localhost\",11211}]},\n                                   {sharding_algorithm,{mero,shard_crc32}},\n                                   {workers_per_shard,1},\n                                   {pool_worker_module,mero_wrk_tcp_txt}]}]).\n\n```\n\nTesting the library against a local memcached server:\n=====================================================\n\n**Warning:** This will erase all the contents of the memcached server it connects to (`\"localhost\"` by default).\n\nTo run tests using a real memcached server, uncomment the test cases at `test/mero_test_with_local_memcached_SUITE.erl`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadroll%2Fmero","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadroll%2Fmero","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadroll%2Fmero/lists"}