MeliGraph.Router (MeliGraph v0.3.0)

Copy Markdown View Source

Roteamento transparente entre o nó local e o nó dono de um grafo (v0.3).

Stateless. Duas responsabilidades:

  • Resolver a rota de cada operação: se o conf está no Registry local (modo :local ou este nó é o dono em :horde) → fast path (chamada direta contra a ETS local, multi-reader, zero Horde/:erpc). Caso contrário → :erpc.call para o nó dono.
  • Executar a operação no dono (run_local/3): roda o mesmo código de hoje (Query, Writer, SegmentManager, ...) contra a ETS local. O que cruza a rede é comando + resultado, nunca acesso ao dado.

Por que :erpc.call e não um GenServer.call central: rotear leitura por um único processo serializaria as leituras (mata o multi-reader). O :erpc.call roda a função num processo novo no dono → ETS concorrente preservada.

Summary

Functions

Corpo de neighbors/4 extraído (rodava inline na API pública). Roda inteiro no dono porque encadeia vários acessos à ETB (IdMap + SegmentManager).

Resolve a rota de um grafo: {:local, conf}, {:remote, owner_node, conf} ou {:error, :graph_unavailable}.

Roteia uma leitura. Local → direto; remoto → :erpc.call no dono. Propaga {:error, :graph_unavailable | :graph_timeout} no caminho remoto.

Roteia uma escrita. Política por op

Executa a operação no nó dono, contra a ETS local. Invocada via :erpc. É o mesmo código de hoje; só muda onde roda.

Types

route()

@type route() ::
  {:local, MeliGraph.Config.t()} | {:remote, node(), MeliGraph.Config.t()}

Functions

do_neighbors(conf, entity_id, direction, opts)

@spec do_neighbors(MeliGraph.Config.t(), term(), :outgoing | :incoming, keyword()) ::
  [term()]

Corpo de neighbors/4 extraído (rodava inline na API pública). Roda inteiro no dono porque encadeia vários acessos à ETB (IdMap + SegmentManager).

resolve_route(name)

@spec resolve_route(atom()) :: route() | {:error, :graph_unavailable}

Resolve a rota de um grafo: {:local, conf}, {:remote, owner_node, conf} ou {:error, :graph_unavailable}.

Em contexto não-distribuído (sem cluster), um grafo ausente localmente levanta ArgumentError — paridade exata com o single-node de hoje.

route_read(name, op, args)

@spec route_read(atom(), atom(), [term()]) :: term()

Roteia uma leitura. Local → direto; remoto → :erpc.call no dono. Propaga {:error, :graph_unavailable | :graph_timeout} no caminho remoto.

route_write(name, op, args)

@spec route_write(atom(), atom(), [term()]) :: term()

Roteia uma escrita. Política por op:

  • insert_edge remoto em testing: :disabled:erpc.cast (fire-and-forget, espelha o GenServer.cast local); em :sync:erpc.call. Grafo indisponível → dropa com warning (a fonte da verdade é o Postgres + on_ready, que faz replay).
  • demais escritas → :erpc.call.

run_local(name, op, args)

@spec run_local(atom(), atom(), [term()]) :: term()

Executa a operação no nó dono, contra a ETS local. Invocada via :erpc. É o mesmo código de hoje; só muda onde roda.