Open-source content management system (CMS) built with Phoenix LiveView. Faster render times to boost SEO performance, even for the most content-heavy pages.
This issue is a discussion and overview of possible strategies to serve pages dynamically.
Beacon needs to serve templates and data for each page that are created at runtime and there are different strategies with its own benefits and trade-offs. In order to compare such strategies, let's list the requirements first:
Requirements
Each page has an associated template and assigns where template is a string compiled to Phoenix.LiveView.Rendered and assigns is a map containing title, description, meta tags, and some other metadata.
Publishing a new version of a page can't break the experience for current visitors, ie: if you have a open page you shouldn't be impacted.
Deploying a site must not require more resources than available, ie: loading resources on the boot process should maintain memory and cpu limits within provisioned limits.
Rendering pages should be as fast as possible.
Recovering from a crash must handle all the reconnects.
Keep in mind such storage is dynamic as new pages are published it must be updated at runtime.
Below is a simplified pseudo-code of how templates and assigns are used in the LiveView that handle all requests:
defmodule PageLive do
def mount(params, session, socket) do
page = load_page(site, path)
{:ok, assign(title: page.title)}
end
def render(assigns) do
page.template
end
end
Essentially the PageLive is a proxy that loads pages and serves the template and data on the regular LiveView workflow.
Strategies
Single Pages Module
One single module containing all templates and all assigns of all pages in this format:
defmodule Pages do
def render("/" = _path) do
~H"<h1>Home</h1>"
end
def assigns("/" = _path) do
%{title: "Home"}
end
def render("/contact" = _path) do
~H"<h1>Contact</h1>"
end
def assigns("/contact" = _path) do
%{title: "Contact"}
end
end
Pros
A single module to manage
Pattern match on each function works as a router
Slightly faster than multiple page modules
Cons
Requires too much resource to load the module causing instability on every page update
No isolation. If one page fails to compile the whole site is impacted and may suffer downtime
We lose the ability to keep the old version of the module when it's recompiled (:code.delete/1)
Simultaneous http requests or simultaneous page publishing may cause more than one compilation process to race each other causing the module being redefined multiple times
Multiple Page Modules
The current strategy in use.
defmodule PageA do
def render do
~H"<h1>Home</h1>"
end
def assigns do
%{title: "Home"}
end
end
defmodule PageB do
def render do
~H"<h1>Contact</h1>"
end
def assigns do
%{title: "Contact"}
end
end
Pros
Isolated. One page failure doesn't affect other pages and the overall site stability
Replacing a module keeps the old version in memory for current processes so it's safer to update pages (individual :code.delete/1 calls)
Cons
Harder to manage multiple modules
Requires a router ETS table to find the module to serve the page
Simultaneous http requests (but not simultaneous page publishing) may cause more than one compilation process to race each other causing the module being redefined multiple times
ETS
Essentially similar to multiple page modules but store pages into ETS, thus have essential the same pros and cons with the biggest difference that it's slower but handles concurrent requests more easily.
Persistent Term
Very similar to ETS but should be a bit faster to read and much slower to write.
Performance is essential and can't be ignored. An initial benchmark shows that modules are faster than persistent_term and ETS.
This issue is a discussion and overview of possible strategies to serve pages dynamically.
Beacon needs to serve templates and data for each page that are created at runtime and there are different strategies with its own benefits and trade-offs. In order to compare such strategies, let's list the requirements first:
Requirements
template
andassigns
wheretemplate
is a string compiled toPhoenix.LiveView.Rendered
andassigns
is a map containing title, description, meta tags, and some other metadata.Keep in mind such storage is dynamic as new pages are published it must be updated at runtime.
Below is a simplified pseudo-code of how templates and assigns are used in the LiveView that handle all requests:
Essentially the
PageLive
is a proxy that loads pages and serves the template and data on the regular LiveView workflow.Strategies
Single Pages Module
One single module containing all templates and all assigns of all pages in this format:
Pros
Cons
:code.delete/1
)Multiple Page Modules
The current strategy in use.
Pros
:code.delete/1
calls)Cons
ETS
Essentially similar to multiple page modules but store pages into ETS, thus have essential the same pros and cons with the biggest difference that it's slower but handles concurrent requests more easily.
Persistent Term
Very similar to ETS but should be a bit faster to read and much slower to write.
Performance is essential and can't be ignored. An initial benchmark shows that modules are faster than persistent_term and ETS.