Pages direction: Iterate on current architecture (esp. improve independence from GitLab Rails app)
- Finish Admin API
- Add Hashed Storage
- Isolate persistence layer if we need availability independent of GitLab Rails: use own DB
Isolate each "deployment" as a transaction
- Geo only needs to replicate each deployment
what I did for
$old_company(explained a little bit in here: http://shipit.resultadosdigitais.com.br/blog/do-apache-ao-go-como-melhoramos-nossas-landing-pages/) was entirely based on the GitHub pages slides I posted above. We had this requirement that the Landing Pages services should be independent of the main application (also a rails monolith), so what we did was we isolated it completely. We create a new API on the "Pages" app that had all the required data there:
- the Virtual Hosts it should handle
- the associated "customer ID" with the Virtual Host (that allowed mapping N:1 Vhosts to customers data)
- each slug we handled (in that service it made sense to have that information stored, so we could handle 404 cheaply, this doesn't work for our Pages)
- an API method to publish a page slug
- an API method to unpublish a page slug (keep on S3 but disable on the database)
- an API method to delete a page slug (remove from the database and delete from S3)
When the user authored a new Landing Page (via the WYSIWYG interface on the Rails app), the last step is to publish it, so they had to select a slug and press a button, that would trigger the API calls and populate the Landing Pages service. We would still have a copy of everything on the Rails app, in order to backfill the Landing Pages services if anything went wrong (or we needed to switch cloud providers).
In our use-case what we need is similar except that we don't want to handle each slug in isolation, but each "deployment" as a transaction.
We should have the assets stored in
/gitlab_pages/:disk_path[0..1]/:disk_path[2..3]/:disk_path/:artifact_idthat allow us to start using a Hashed Storage (which will help us with the locality distribution for an object storage and also allow projects to be renamed / transfered to another group.
:artifact_idallows us to do a blue/green page deployment, so we only switch to the new artifact_id when it is fully published. Also if publishing it fails, we don't do the switch and we don't take the website down.
Publishing pages should be a separate step as today:
- download the artifact from previous builds, publish it to the
/gitlab_pages(that can be either a shared folder on a local disk, on a NFS server / cloud disk, or to an object storage).
If we need to have pages up even when GitLab is down, we could also isolate the persistence layer: use its own database for pages (either a PostgreSQL separate database, that can be on the same PG instance for small setups or an entire new one for systems with high traffic as gitlab dot com, or we can also use a persistent redis, following the same logic above, using a separate database in the same instance for small setups or its own instance for bigger ones).
In both situations above, we could either have the rails acessing these database directly or have our pages daemon implement its own internal API, that the main application will use to:
- register a new vhost and associate it with the
- register current artifact_id for the project_id (so we can switch the deployments)
- allow defining the canonical domain for the :project_id (if request doesn't come from it, redirect to the canonical one) etc.
Alternatively we can also do this entirely using the filesystem (or the object storage), so we would store the one config file for each vhost, and pages app can read that file based on the requested vhost and take the decisions from there. (there are advantages and disadvantages on both. The file approach means we should limit the ammount of vhosts we are going to support as updating any configuration or deploying means an
The file approach should also require a much smarter cache mechanism on the pages application so we don't consume too many object storage requests.
for the Geo usecase, this external database would fit perfectly, as we could replicate the same process on the secondary as well:
- When a new page deployment happens, we create a Geo event
- On the secondary, we download the
artifact.zipfor that deployment and then recreate the deployment on that node by doing the same steps: extract, upload and switch from localdatabase the current :artifact_id.
Any update on the domain configuration should also create a Geo event, so we can replicate the changes on Pages database as well at its own pace.