How to start Xandra as child process

I am trying to use Xandra for my Phoenix project for which I am connecting to Cassandra like

{:ok, conn} = Xandra.start_link([
  nodes: ["localhost:9042"]
])

Every time I am suppose to run a query I need to start_link, Is there a way I can provide all connection details in config file and it runs once the app is started?

Thanks

Anyone with help is appreciated.

Not in the config file, but in (one of) the supervisor(s) of your application. See here.

However, how one obtains a conn datatype to pass to all of the other functions when using Xandra (or another DBConnection-implementation for that matter) inside a supervision tree is something I cannot immediately figure out.

Maybe library author @whatyouhide can shed some light on this?

EDIT: It’s probably {Xandra, name: MyXandraConnection}, as now the connection GenServer is registered node-wide with that particular name. Then you can use MyXandraConnection everywhere you passed a conn PID before.

Thanks @Qqwy for the details analysis but I am relatively new to this stuff.

I am not sure where the connection details would go where it will be started the first time.

Probably something like {Xandra, name: MyXandraConnection, other: option, here: probably}

You can can read the configuration from the application environment manually:

# in config/config.exs (or in an environment-specific config file)
config :my_app, :my_xandra, options

and then:

# in my_app/application.ex
xandra_config = Application.fetch_env!(:my_app, :my_xandra)

children = [
  # ...,
  {Xandra, xandra_config}
]
1 Like

Thank you @whatyouhide for the solution but I am still getting few errors with this approach.

dev.exs

config :my_app, :xandra,
  nodes: System.get_env("CASSANDRA_HOST"),
  authentication: {Xandra.Authenticator.Password, 
        [
          username: System.get_env("CASSANDRA_USER"),
          password: System.get_env("CASSANDRA_PASSWORD")
        ]
  },
  pool: DBConnection.Poolboy,
  pool_size: 10

application.exs

children = [
      # Start the Ecto repository
      supervisor(MyApp.Repo, []),
      # Start the endpoint when the application starts
      supervisor(MyApp.Web.Endpoint, []),
      worker(Cachex, [:app_cache, []]),
      worker(MyApp.Scheduler, []),
      {Xandra, Application.fetch_env!(:my_app, :xandra)}
    ]

Stacktrace

(Mix) Could not start application my_app: MyApp.Application.start(:normal, []) returned an error: shutdown: failed to start child: Xandra
    ** (EXIT) an exception was raised:
        ** (ArgumentError) multi-node use requires Xandra.Cluster instead of Xandra
            (xandra) lib/xandra.ex:1027: Xandra.parse_nodes_option/1
            (xandra) lib/xandra.ex:1010: Xandra.convert_nodes_options_to_address_and_port/1
            (xandra) lib/xandra.ex:336: Xandra.start_link/1
            (stdlib) supervisor.erl:379: :supervisor.do_start_child_i/3
            (stdlib) supervisor.erl:365: :supervisor.do_start_child/2
            (stdlib) supervisor.erl:349: anonymous fn/3 in :supervisor.start_children/2
            (stdlib) supervisor.erl:1157: :supervisor.children_map/4
            (stdlib) supervisor.erl:315: :supervisor.init_children/2
            (stdlib) gen_server.erl:374: :gen_server.init_it/2
            (stdlib) gen_server.erl:342: :gen_server.init_it/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
49: :proc_lib.init_p_do_apply/3

Then I changed the info in application.exs to

children = [
      # Start the Ecto repository
      supervisor(MyApp.Repo, []),
      # Start the endpoint when the application starts
      supervisor(MyApp.Web.Endpoint, []),
      worker(Cachex, [:app_cache, []]),
      worker(MyApp.Scheduler, []),
      {Xandra.Cluster, Application.fetch_env!(:my_app, :xandra)}
    ]

I got new exception

Could not start application my_app: MyApp.Application.start(:normal, []) returned an error: shutdown: failed to start child: Xandra.Cluster
    ** (EXIT) an exception was raised:
        ** (Protocol.UndefinedError) protocol Enumerable not implemented for "localhost:9042". This protocol is implemented for: Ecto.Adapters.SQL.Stream, Postgrex.Stream, Xandra.PageStream, Xandra.Page, DBConnection.Stream, DBConnection.PrepareStream, Timex.Interval, HashSet, Range, Map, Function, List, Stream, Date.Range, HashDict, GenEvent.Stream, MapSet, File.Stream, IO.Stream
            (elixir) /Users/sahilpaudel/.kiex/builds/elixir-git/lib/elixir/lib/enum.ex:1: Enumerable.impl_for!/1
            (elixir) /Users/sahilpaudel/.kiex/builds/elixir-git/lib/elixir/lib/enum.ex:141: Enumerable.reduce/3
            (elixir) lib/enum.ex:3015: Enum.map/2
            (xandra) lib/xandra/cluster.ex:204: Xandra.Cluster.start_link/1
            (stdlib) supervisor.erl:379: :supervisor.do_start_child_i/3
            (stdlib) supervisor.erl:365: :supervisor.do_start_child/2
            (stdlib) supervisor.erl:349: anonymous fn/3 in :supervisor.start_children/2
            (stdlib) supervisor.erl:1157: :supervisor.children_map/4
            (stdlib) supervisor.erl:315: :supervisor.init_children/2
            (stdlib) gen_server.erl:374: :gen_server.init_it/2
            (stdlib) gen_server.erl:342: :gen_server.init_it/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

My env file

export CASSANDRA_HOST=localhost:9042
export CASSANDRA_USER=
export CASSANDRA_PASSWORD=

Thanks.

The nodes option requires a list of strings, but you are reading a single string from the system environment. Use nodes: [System.get_env("CASSANDRA_HOST")].

1 Like

Great, it worked.

Final question how can I execute a query now?

Thanks.

You need to pass the name option to give a name to the Xadnra pool (such as name: MyXandra) and then you can use Xandra.execute or similar functions to execute queries. Refer to the docs for more information :slight_smile:

1 Like