Skip to content

Commit 6dcf5b0

Browse files
committed
fix: improve crud remote calls
This approach prevents sending more information than needed reducing the amount of data send via gen_rpc. We also remove some unnecessary calls to primary in the delete controller operation
1 parent 118dfc5 commit 6dcf5b0

File tree

7 files changed

+88
-83
lines changed

7 files changed

+88
-83
lines changed

lib/realtime/api.ex

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,13 @@ defmodule Realtime.Api do
115115
Logger.debug("create_tenant #{inspect(attrs, pretty: true)}")
116116
tenant_id = Map.get(attrs, :external_id) || Map.get(attrs, "external_id")
117117

118-
%Tenant{}
119-
|> Tenant.changeset(attrs)
120-
|> region_aware_write(:insert, tenant_id)
118+
if master_region?() do
119+
%Tenant{}
120+
|> Tenant.changeset(attrs)
121+
|> Repo.insert()
122+
else
123+
call(:create_tenant, [attrs], tenant_id)
124+
end
121125
end
122126

123127
@doc """
@@ -132,9 +136,18 @@ defmodule Realtime.Api do
132136
{:error, %Ecto.Changeset{}}
133137
134138
"""
135-
def update_tenant(%Tenant{} = tenant, attrs) do
139+
@spec update_tenant(binary() | Tenant.t(), map()) :: {:ok, Tenant.t()} | {:error, term()}
140+
def update_tenant(tenant_id, attrs) when is_binary(tenant_id) do
141+
replica = Replica.replica()
142+
tenant = replica.get_by(Tenant, external_id: tenant_id)
143+
update_tenant(tenant, attrs)
144+
end
145+
146+
def update_tenant(%Tenant{external_id: external_id} = tenant, attrs) do
136147
changeset = Tenant.changeset(tenant, attrs)
137-
updated = region_aware_write(changeset, :update, tenant.external_id)
148+
149+
updated =
150+
if master_region?(), do: Repo.update(changeset), else: call(:update_tenant, [external_id, attrs], external_id)
138151

139152
case updated do
140153
{:ok, tenant} ->
@@ -150,38 +163,23 @@ defmodule Realtime.Api do
150163
updated
151164
end
152165

153-
@doc """
154-
Deletes a tenant.
155-
156-
## Examples
157-
158-
iex> delete_tenant(tenant)
159-
{:ok, %Tenant{}}
160-
161-
iex> delete_tenant(tenant)
162-
{:error, %Ecto.Changeset{}}
163-
164-
"""
165-
def delete_tenant(%Tenant{} = tenant), do: Repo.delete(tenant)
166-
167166
@spec delete_tenant_by_external_id(String.t()) :: boolean()
168167
def delete_tenant_by_external_id(id) do
169-
from(t in Tenant, where: t.external_id == ^id)
170-
|> region_aware_write(:delete_all, id)
171-
|> case do
172-
{num, _} when num > 0 -> true
173-
_ -> false
168+
if master_region?() do
169+
from(t in Tenant, where: t.external_id == ^id)
170+
|> Repo.delete_all()
171+
|> case do
172+
{num, _} when num > 0 -> true
173+
_ -> false
174+
end
175+
else
176+
call(:delete_tenant_by_external_id, [id], id)
174177
end
175178
end
176179

177-
@spec get_tenant_by_external_id(String.t(), atom()) :: Tenant.t() | nil
178-
def get_tenant_by_external_id(external_id, repo \\ :replica)
179-
when repo in [:primary, :replica] do
180-
repo =
181-
case repo do
182-
:primary -> Repo
183-
:replica -> Replica.replica()
184-
end
180+
@spec get_tenant_by_external_id(String.t()) :: Tenant.t() | nil
181+
def get_tenant_by_external_id(external_id) do
182+
repo = Replica.replica()
185183

186184
Tenant
187185
|> repo.get_by(external_id: external_id)
@@ -195,26 +193,36 @@ defmodule Realtime.Api do
195193
end
196194

197195
def rename_settings_field(from, to) do
198-
for extension <- list_extensions("postgres_cdc_rls") do
199-
{value, settings} = Map.pop(extension.settings, from)
200-
new_settings = Map.put(settings, to, value)
201-
202-
extension
203-
|> Changeset.cast(%{settings: new_settings}, [:settings])
204-
|> region_aware_write(:update!, extension.external_id)
196+
if master_region?() do
197+
for extension <- list_extensions("postgres_cdc_rls") do
198+
{value, settings} = Map.pop(extension.settings, from)
199+
new_settings = Map.put(settings, to, value)
200+
201+
extension
202+
|> Changeset.cast(%{settings: new_settings}, [:settings])
203+
|> Repo.update()
204+
end
205+
else
206+
call(:rename_settings_field, [from, to], from)
205207
end
206208
end
207209

210+
@spec preload_counters(nil | Realtime.Api.Tenant.t(), any()) :: nil | Realtime.Api.Tenant.t()
208211
@doc """
209212
Updates the migrations_ran field for a tenant.
210213
"""
211214
@spec update_migrations_ran(binary(), integer()) :: {:ok, Tenant.t()} | {:error, term()}
212215
def update_migrations_ran(external_id, count) do
213-
external_id
214-
|> Cache.get_tenant_by_external_id()
215-
|> Tenant.changeset(%{migrations_ran: count})
216-
|> region_aware_write(:update!, external_id)
217-
|> tap(fn _ -> Cache.distributed_invalidate_tenant_cache(external_id) end)
216+
if master_region?() do
217+
tenant = Cache.get_tenant_by_external_id(external_id)
218+
219+
tenant
220+
|> Tenant.changeset(%{migrations_ran: count})
221+
|> Repo.update()
222+
|> tap(fn _ -> Cache.distributed_invalidate_tenant_cache(external_id) end)
223+
else
224+
call(:update_migrations_ran, [external_id, count], external_id)
225+
end
218226
end
219227

220228
def preload_counters(nil), do: nil
@@ -257,21 +265,13 @@ defmodule Realtime.Api do
257265

258266
defp maybe_restart_db_connection(_changeset), do: nil
259267

260-
defp local_call? do
268+
defp master_region? do
261269
region = Application.get_env(:realtime, :region)
262270
master_region = Application.get_env(:realtime, :master_region) || region
263271
region == master_region
264272
end
265273

266-
defp region_aware_write(%struct{} = argument, operation, tenant_id) when struct in [Changeset, Ecto.Query] do
267-
if local_call?(),
268-
do: local_call(operation, [argument], tenant_id),
269-
else: remote_call(operation, [argument], tenant_id)
270-
end
271-
272-
defp local_call(operation, args, _tenant_id), do: apply(Realtime.Repo, operation, args)
273-
274-
defp remote_call(operation, args, tenant_id) do
274+
defp call(operation, args, tenant_id) do
275275
master_region = Application.get_env(:realtime, :master_region)
276276

277277
with {:ok, master_node} <- Nodes.node_from_region(master_region, self()),
@@ -281,7 +281,7 @@ defmodule Realtime.Api do
281281
end
282282

283283
defp wrapped_call(master_node, operation, args, tenant_id) do
284-
case GenRpc.call(master_node, Realtime.Repo, operation, args, tenant_id: tenant_id) do
284+
case GenRpc.call(master_node, __MODULE__, operation, args, tenant_id: tenant_id) do
285285
{:error, :rpc_error, reason} -> {:error, reason}
286286
{:error, reason} -> {:error, reason}
287287
result -> {:ok, result}

lib/realtime/repo_replica.ex

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,9 @@ defmodule Realtime.Repo.Replica do
5151
# Do not create module if replica isn't set or configuration is not present
5252
cond do
5353
is_nil(replica) ->
54-
Logger.info("Replica region not found, defaulting to Realtime.Repo")
5554
Realtime.Repo
5655

5756
is_nil(replica_conf) ->
58-
Logger.info("Replica config not found for #{region} region")
5957
Realtime.Repo
6058

6159
true ->

lib/realtime_web/controllers/tenant_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ defmodule RealtimeWeb.TenantController do
192192
def delete(conn, %{"tenant_id" => tenant_id}) do
193193
stop_all_timeout = Enum.count(PostgresCdc.available_drivers()) * 1_000
194194

195-
with %Tenant{} = tenant <- Api.get_tenant_by_external_id(tenant_id, :primary),
195+
with %Tenant{} = tenant <- Api.get_tenant_by_external_id(tenant_id),
196196
_ <- Tenants.suspend_tenant_by_external_id(tenant_id),
197197
true <- Api.delete_tenant_by_external_id(tenant_id),
198198
true <- Cache.distributed_invalidate_tenant_cache(tenant_id),

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Realtime.MixProject do
44
def project do
55
[
66
app: :realtime,
7-
version: "2.66.1",
7+
version: "2.66.2",
88
elixir: "~> 1.18",
99
elixirc_paths: elixirc_paths(Mix.env()),
1010
start_permanent: Mix.env() == :prod,

test/integration/region_aware_routing_test.exs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ defmodule Realtime.Integration.RegionAwareRoutingTest do
4949

5050
Mimic.expect(Realtime.GenRpc, :call, fn node, mod, func, args, opts ->
5151
assert node == master_node
52-
assert mod == Realtime.Repo
53-
assert func == :insert
52+
assert mod == Realtime.Api
53+
assert func == :create_tenant
5454
assert opts[:tenant_id] == external_id
5555

5656
call_original(GenRpc, :call, [node, mod, func, args, opts])
@@ -80,16 +80,16 @@ defmodule Realtime.Integration.RegionAwareRoutingTest do
8080
Realtime.GenRpc
8181
|> Mimic.expect(:call, fn node, mod, func, args, opts ->
8282
assert node == master_node
83-
assert mod == Realtime.Repo
84-
assert func == :insert
83+
assert mod == Realtime.Api
84+
assert func == :create_tenant
8585
assert opts[:tenant_id] == tenant_attrs["external_id"]
8686

8787
call_original(GenRpc, :call, [node, mod, func, args, opts])
8888
end)
8989
|> Mimic.expect(:call, fn node, mod, func, args, opts ->
9090
assert node == master_node
91-
assert mod == Realtime.Repo
92-
assert func == :update
91+
assert mod == Realtime.Api
92+
assert func == :update_tenant
9393
assert opts[:tenant_id] == tenant_attrs["external_id"]
9494

9595
call_original(GenRpc, :call, [node, mod, func, args, opts])
@@ -123,16 +123,16 @@ defmodule Realtime.Integration.RegionAwareRoutingTest do
123123
Realtime.GenRpc
124124
|> Mimic.expect(:call, fn node, mod, func, args, opts ->
125125
assert node == master_node
126-
assert mod == Realtime.Repo
127-
assert func == :insert
126+
assert mod == Realtime.Api
127+
assert func == :create_tenant
128128
assert opts[:tenant_id] == tenant_attrs["external_id"]
129129

130130
call_original(GenRpc, :call, [node, mod, func, args, opts])
131131
end)
132132
|> Mimic.expect(:call, fn node, mod, func, args, opts ->
133133
assert node == master_node
134-
assert mod == Realtime.Repo
135-
assert func == :delete_all
134+
assert mod == Realtime.Api
135+
assert func == :delete_tenant_by_external_id
136136
assert opts[:tenant_id] == tenant_attrs["external_id"]
137137

138138
call_original(GenRpc, :call, [node, mod, func, args, opts])
@@ -164,16 +164,16 @@ defmodule Realtime.Integration.RegionAwareRoutingTest do
164164
Realtime.GenRpc
165165
|> Mimic.expect(:call, fn node, mod, func, args, opts ->
166166
assert node == master_node
167-
assert mod == Realtime.Repo
168-
assert func == :insert
167+
assert mod == Realtime.Api
168+
assert func == :create_tenant
169169
assert opts[:tenant_id] == tenant_attrs["external_id"]
170170

171171
call_original(GenRpc, :call, [node, mod, func, args, opts])
172172
end)
173173
|> Mimic.expect(:call, fn node, mod, func, args, opts ->
174174
assert node == master_node
175-
assert mod == Realtime.Repo
176-
assert func == :update!
175+
assert mod == Realtime.Api
176+
assert func == :update_migrations_ran
177177
assert opts[:tenant_id] == tenant_attrs["external_id"]
178178

179179
call_original(GenRpc, :call, [node, mod, func, args, opts])
@@ -184,7 +184,7 @@ defmodule Realtime.Integration.RegionAwareRoutingTest do
184184
new_migrations_ran = 5
185185
result = Api.update_migrations_ran(tenant.external_id, new_migrations_ran)
186186

187-
assert %Tenant{} = updated = result
187+
assert {:ok, updated} = result
188188
assert updated.migrations_ran == new_migrations_ran
189189

190190
reloaded = Realtime.Repo.get(Tenant, tenant.id)

test/realtime/api_test.exs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,20 @@ defmodule Realtime.ApiTest do
109109
describe "update_tenant/2" do
110110
setup [:create_tenants]
111111

112+
test "valid data updates the tenant using external_id", %{tenants: [tenant | _]} do
113+
update_attrs = %{
114+
external_id: tenant.external_id,
115+
jwt_secret: "some updated jwt_secret",
116+
name: "some updated name"
117+
}
118+
119+
assert {:ok, %Tenant{} = tenant} = Api.update_tenant(tenant.external_id, update_attrs)
120+
assert tenant.external_id == tenant.external_id
121+
122+
assert tenant.jwt_secret == Crypto.encrypt!("some updated jwt_secret")
123+
assert tenant.name == "some updated name"
124+
end
125+
112126
test "valid data updates the tenant", %{tenants: [tenant | _]} do
113127
update_attrs = %{
114128
external_id: tenant.external_id,
@@ -232,14 +246,6 @@ defmodule Realtime.ApiTest do
232246
end
233247
end
234248

235-
describe "delete_tenant/1" do
236-
test "deletes the tenant" do
237-
tenant = tenant_fixture()
238-
assert {:ok, %Tenant{}} = Api.delete_tenant(tenant)
239-
assert_raise Ecto.NoResultsError, fn -> Api.get_tenant!(tenant.id) end
240-
end
241-
end
242-
243249
describe "delete_tenant_by_external_id/1" do
244250
test "deletes the tenant" do
245251
tenant = tenant_fixture()

test/test_helper.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ end
1515

1616
{:ok, _pid} = Containers.start_link(max_cases)
1717

18-
for tenant <- Api.list_tenants(), do: Api.delete_tenant(tenant)
18+
for tenant <- Api.list_tenants(), do: Api.delete_tenant_by_external_id(tenant.external_id)
1919

2020
tenant_name = "dev_tenant"
2121
tenant = Containers.initialize(tenant_name)
@@ -53,6 +53,7 @@ Mimic.copy(Realtime.Database)
5353
Mimic.copy(Realtime.GenCounter)
5454
Mimic.copy(Realtime.GenRpc)
5555
Mimic.copy(Realtime.Nodes)
56+
Mimic.copy(Realtime.Repo.Replica)
5657
Mimic.copy(Realtime.RateCounter)
5758
Mimic.copy(Realtime.Tenants.Authorization)
5859
Mimic.copy(Realtime.Tenants.Cache)

0 commit comments

Comments
 (0)