{"id":20751392,"url":"https://github.com/superfly/fly_rpc_elixir","last_synced_at":"2025-08-21T18:33:01.860Z","repository":{"id":42981144,"uuid":"412172517","full_name":"superfly/fly_rpc_elixir","owner":"superfly","description":"Makes it easier to build distributed Elixir applications on the Fly.io platform. Adds region awareness to an app and makes it easy to perform RPC calls in other Fly.io regions.","archived":false,"fork":false,"pushed_at":"2023-08-15T20:11:14.000Z","size":73,"stargazers_count":54,"open_issues_count":0,"forks_count":3,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-12-17T02:36:15.955Z","etag":null,"topics":["distributed","elixir","global","rpc"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/fly_rpc","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/superfly.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2021-09-30T18:08:27.000Z","updated_at":"2024-11-24T20:23:19.000Z","dependencies_parsed_at":"2023-09-30T07:01:02.088Z","dependency_job_id":null,"html_url":"https://github.com/superfly/fly_rpc_elixir","commit_stats":{"total_commits":55,"total_committers":4,"mean_commits":13.75,"dds":0.07272727272727275,"last_synced_commit":"97e01c76bb920ce37ba15d937104461b122c4400"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superfly%2Ffly_rpc_elixir","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superfly%2Ffly_rpc_elixir/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superfly%2Ffly_rpc_elixir/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superfly%2Ffly_rpc_elixir/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/superfly","download_url":"https://codeload.github.com/superfly/fly_rpc_elixir/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230527866,"owners_count":18240051,"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":["distributed","elixir","global","rpc"],"created_at":"2024-11-17T08:33:21.124Z","updated_at":"2024-12-20T03:09:16.036Z","avatar_url":"https://github.com/superfly.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fly RPC\n\nHelps a clustered Elixir application know what [Fly.io](https:://fly.io) region it is deployed in, if that is the primary region, and provides features for executing a function through RPC (Remote Procedure Call) on another node by specifying the region to run it in. It is specifically designed to make it easier to execute code in the \"primary\" region.\n\nThis library can be used outside of the [Fly.io](https://fly.io) platform as well. In order to work, the nodes need to be clustered and then set `PRIMARY_REGION` and `MY_REGION` ENV values. Everything else works the same. When running on [Fly.io](https://fly.io), the `FLY_REGION` ENV value provided by the platform is used for `MY_REGION`.\n\nThe \"primary\" region refers to which region your primary, writeable database lives in. Writes to the primary database should ideally be performed from a server running in, or near, the primary region.\n\n[Online Documentation](https://hexdocs.pm/fly_rpc)\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `fly_rpc` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:fly_rpc, \"~\u003e 0.3.0\"}\n  ]\nend\n```\n\nThrough ENV configuration, you can to tell the app which region is the \"primary\" region.\n\n`fly.toml`\n\nThis example configuration says that the Sydney Australia region is the\n\"primary\" region.\n\n```yaml\n[env]\n  PRIMARY_REGION = \"syd\"\n```\n\nAdd `Fly.RPC` to your application's supervision tree.\n\n```elixir\ndefmodule MyApp.Application do\n  use Application\n\n  def start(_type, _args) do\n    # ...\n\n    children = [\n      # Start the RPC server\n      {Fly.RPC, []},\n      #...\n    ]\n\n    # ...\n  end\nend\n```\n\nThis starts a GenServer that reaches out to other nodes in the cluster to learn\nwhat Fly.io regions they are located in. The GenServer caches those results in a\nlocal ETS table for fast access.\n\n## Usage\n\nThe Fly.io platform already provides and ENV value of `FLY_REGION` which this library accesses and uses as the `MY_REGION`. When using this library on a platform other than [fly.io](https://fly.io), you can supply the ENV `MY_REGION` to identify what \"region\" the running instance is in. Think of the value as a text label of however you want to identify where it's running.\n\n```elixir\nFly.RPC.primary_region()\n#=\u003e \"syd\"\n\nFly.RPC.my_region()\n#=\u003e \"lax\"\n\nFly.RPC.is_primary?()\n#=\u003e false\n```\n\nThe real benefit comes in using the `Fly.RPC` module.\n\n```elixir\nFly.RPC.rpc_region(\"hkg\", String, :upcase, [\"fly\"])\n#=\u003e \"FLY\"\n\nFly.RPC.rpc_region(Fly.primary_region(), String, :upcase, [\"fly\"])\n#=\u003e \"FLY\"\n\nFly.RPC.rpc_region(:primary, String, :upcase, [\"fly\"])\n#=\u003e \"FLY\"\n```\n\nUnderneath the call, it's using `Node.spawn_link/4`. This spawns a new process on a node in the desired region. Normally, that spawn becomes an asynchronous process and what you get back is a `pid`. In this case, the call executes on the other node and the caller is blocked until the result is received or the request times out.\n\nBy blocking the process, this makes it much easier to reason about your application code.\n\n\nThe following is a convenience function for performing work on the primary.\n\n```elixir\nFly.RPC.rpc_primary(String, :upcase, [\"fly\"])\n#=\u003e \"FLY\"\n```\n\n## Local Development\n\nWhen doing local development, the local and primary regions will be set to \"local\" by default. However, if we want to simulate running in a non-primary region locally, we can set the `MY_REGION` and `PRIMARY_REGION` environment variables explicitly:\n\n- `MY_REGION` - You tell the library what region it is running in.\n- `PRIMARY_REGION` - You tell the library which region is the \"primary\".\n\nBy default, the value `\"local\"` is used for the regions. This works perfectly for local development as we are, effectively, the primary anyway.\n\n## Explicitly Set the Region\n\nWhen running locally and we explicitly want to set the regions, the `MY_REGION` isn't set since the app isn't on Fly.io. Also, the `PRIMARY_REGION` specified in our `fly.toml` file isn't referenced. We just need a way to set those values when the application is running locally.\n\nI like using [direnv](https://direnv.net/) to automatically set and load ENV values when I enter specific directories. Using `direnv`, you can create a file named `.envrc` in your project directory. Add the following lines:\n\n```\nexport MY_REGION=xyz\nexport PRIMARY_REGION=xyz\n```\n\nThis tells the app that it's running in the primary region called \"xyz\". It will connect to the database and perform writes directly.\n\nAnother option is to start you application like this:\n\n```\nMY_REGION=xyz PRIMARY_REGION=xyz iex -S mix phx.server\n```\n\nYou can also create a bash script file named `start` and have it perform the above command.\n\n## Production Environment\n\n### Prevent temporary outages during deployments\n\nWhen deploying on [Fly.io](https://fly.io), a new instance is rolled out before removing the old instance. This creates a period of time where both new and old instances are deployed together. By default, when deploying a Phoenix application, a new BEAM cookie is generated for each deployment. When the new instance rolls out with a new BEAM cookie, the old and new instances will not cluster together. BEAM instances must have the same cookie in order to connect. This is by design.\n\nThis means a newly deployed application running in a secondary region using [fly_postgres](https://github.com/superfly/fly_postgres_elixir) is unable to perform writes to the older application running in the primary region. It is possible for writes to fail during that rollout window.\n\nTo prevent this problem, the BEAM cookie can be explicitly set instead of using a randomly generated one for new builds. When explicitly set, the newly deployed application is still able to connect and cluster with the older application running in the primary region.\n\nHere is a guide to setting a static cookie for your project that is written into the code itself. This is fine to do because the cookie isn't considered a secret used for security.\n\n[fly.io/docs/app-guides/elixir-static-cookie/](https://fly.io/docs/app-guides/elixir-static-cookie/)\n\nWhen the cookie is static and unchanged from one deployment to the next, then applications can continue to cluster and access the applications running in primary region.\n\n### Where Did My Function Go?\n\nWhen deploying on [Fly.io](https://fly.io), a new instance is rolled out before removing the old instance. This creates a period of time where both new and old instances are deployed together.\n\nIn this scenario, let's assume:\n- Node A is an old node.\n- Node B is a new node.\n\nNode A attempts to execute a function on Node B. Node B contains new code and that function doesn't exist as called. Perhaps the function was renamed, the arity changed, a pattern match changed or it's a new function.\n\nWhatever the reason, we now have a situation where the function we want to call fails because it doesn't exist the way we expect it on the new node in the cluster.\n\nBe aware that this _can_ happen. It may cause a single request to fail and that may be fine. We can take steps in our applications to avoid any interruption if it's a critical function that needs to change.\n\nFor critical functions, the following pattern can be used:\n- Create the new, changed function\n- Maintain backward compatibility with another function (if applicable)\n- Deploy the new code moving all instances to use the new, desired function. During the deploy, the backward compatible function prevent breakage.\n- A later deploy removes the backward compatible function.\n\n## Features\n\n- [ ] Instrument with telemetry\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperfly%2Ffly_rpc_elixir","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuperfly%2Ffly_rpc_elixir","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperfly%2Ffly_rpc_elixir/lists"}