mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Napkin math shows that we can save substantial memory (~3x or more) on the API nodes as connected clients/gateways grow if we just store the fields we need in order to keep the client and gateway state maintained in the channel pids. To facilitate this, we create new `Cacheable` structs that represent their `Domain` cousins, which use byte arrays for `id`s and strip out unused fields. Additionally, all business logic involved with maintaining these caches is now contained within two modules: `Domain.Cache.Client` and `Domain.Cache.Gateway`, and type specs have been added to aid in static analysis and code documentation. Comprehensive testing is now added not only for the cache modules, but for their associated channel modules as well to ensure we handle different kinds of edge cases gracefully. The `Events` nomenclature was renamed to `Changes` to better name what we are doing: Change-Data-Capture. Lastly, the following related changes are included in this PR since they were "in the way" so to speak of getting this done: - We save the last received LSN in each channel and drop the `change` with a warning if we receive it twice in a row, or we receive it out of order - The client/gateway version compatibility calculations have been moved to `Domain.Resources` and `Domain.Gateways` and have been simplified to make them easier to understand and maintain going forward. Related: #10174 Fixes: #9392 Fixes: #9965 Fixes: #9501 Fixes: #10227 --------- Signed-off-by: Jamil <jamilbk@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
58 lines
2.0 KiB
Elixir
58 lines
2.0 KiB
Elixir
defmodule Domain.Changes.Hooks.Resources do
|
|
@behaviour Domain.Changes.Hooks
|
|
alias Domain.{Changes.Change, Flows, PubSub, Resources}
|
|
import Domain.SchemaHelpers
|
|
|
|
@impl true
|
|
def on_insert(lsn, data) do
|
|
resource = struct_from_params(Resources.Resource, data)
|
|
change = %Change{lsn: lsn, op: :insert, struct: resource}
|
|
|
|
PubSub.Account.broadcast(resource.account_id, change)
|
|
end
|
|
|
|
@impl true
|
|
|
|
# Soft-delete - process as delete
|
|
def on_update(lsn, %{"deleted_at" => nil} = old_data, %{"deleted_at" => deleted_at})
|
|
when not is_nil(deleted_at) do
|
|
on_delete(lsn, old_data)
|
|
end
|
|
|
|
# Regular update
|
|
def on_update(lsn, old_data, data) do
|
|
old_resource = struct_from_params(Resources.Resource, old_data)
|
|
resource = struct_from_params(Resources.Resource, data)
|
|
change = %Change{lsn: lsn, op: :update, old_struct: old_resource, struct: resource}
|
|
|
|
# Breaking updates
|
|
|
|
# This is a special case - we need to delete related flows because connectivity has changed
|
|
# Gateway _does_ handle resource filter changes so we don't need to delete flows
|
|
# for those changes - they're processed by the Gateway channel pid.
|
|
|
|
# The Gateway channel will process these flow deletions and re-authorize the flow.
|
|
# However, the gateway will also react to the resource update and send reject_access
|
|
# so that the Gateway's state is updated correctly, and the client can create a new flow.
|
|
if old_resource.ip_stack != resource.ip_stack or
|
|
old_resource.type != resource.type or
|
|
old_resource.address != resource.address do
|
|
Flows.delete_flows_for(resource)
|
|
end
|
|
|
|
PubSub.Account.broadcast(resource.account_id, change)
|
|
end
|
|
|
|
@impl true
|
|
def on_delete(lsn, old_data) do
|
|
resource = struct_from_params(Resources.Resource, old_data)
|
|
change = %Change{lsn: lsn, op: :delete, old_struct: resource}
|
|
|
|
# TODO: Hard delete
|
|
# This can be removed upon implementation of hard delete
|
|
Flows.delete_flows_for(resource)
|
|
|
|
PubSub.Account.broadcast(resource.account_id, change)
|
|
end
|
|
end
|