A web portal where to upload photos: Photobook

General design schema.

Why this project?

I was throwing a farewell party and I liked the idea to create a memory book. I had been participating other parties where a (concrete) book was available to the guests to write something or attach photos. But, hey, we are in 2022 and I do not like to have a real book that takes space in my messy home.

So, I decided to have a virtual photo book. I looked around for some existing solutions, but I had not found something that was simple and displaying immediately the new photo. As I have a projector in the living room that can browse webpages, I wanted to leverage the projector to show instantaneously the new photos.

Because I had not found anything good and I like coding, I chose to create a web portal on my own.

The project is downloadable from the URL in the subtitle.

Features

Here a collection of the features:

  • simple, no login at all;
  • the photos on the homepage should be shown immediately after the upload;
  • the photo’s author can edit/delete own photos (a simple cookie will keep track of the user);
  • server admin can inhibit the photo upload;
  • quite scalable.

System description

The system is composed mainly of 3 components: Backend Server (where the logic is implemented), Frontend Server (set of JavaScript and static resources), and Image Server (static file server).

Then, we have aside components: Redis server (to have message distribution among processes), database (to store photos metadata).

Backend Server

I used Python + Flask to implement the API. The following endpoints are used to:

  • upload/list/change photos /api/new_photo /api/photos /api/photo/<id> ;
  • get an unique identifier /api/uid ;
  • notify new events /api/events .

Frontend Server

I used Vue.Js to create the front pages. There are the following views (term borrowed from Vue.JS):

  • Homepage, where to show the uploaded photos / ;
  • Upload page, where to upload the photo /upload ;
  • Edit page, where to change or delete own photos /edit .

Image Server

It is just a Nginx that makes available a folder that contains the uploaded photos.

Scenarios

Here I want to illustrate what is happening when the photos are shown/uploaded/deleted. I think seeing the components in action is the best way to understand them. So I created the following animations.

Showing photos

Animation that shows how the photos are retrieved in the homepage.

When requesting the homepage, these things happen:

  1. the device is kept awake using a loop video (this step is not shown in the animation);
  2. a long polling connection is established to receive Server Side Events (SSE) (this step is not shown in the animation);
  3. the Backend Server is queried for retrieving the latest photos references;
  4. the timestamp of the latest photo is stored to use it again in step 3;
  5. the actual photos are downloaded from the Image Server;
  6. steps 3, 4, and 5 are repeated every time the client receive a proper SSE message.

Uploading a photo

Animation that shows the photo uploading.

When uploading a photo, these things happen:

  1. a randomly generated uid is retrieved from the Backend Server and saved in a cookie (not shown in the animation);
  2. a POST method upload the photo, description, author and uid;
  3. the Backend Server creates metadata for the image, it saves it in the database, it saves the image in the Image Server storage;
  4. the Backend Server notifies all the Backend Server instances through Memcache (i.e. Redis);
  5. all clients connected to the SSE subscription are notified of a new photo;
  6. the new SSE event leads to step 3 of the above “showing photo” scenario (as explained in step 6 of the same section).

Editing or deleting a photo

Animation that shows the photo deletion.

When editing a photo, these things happen:

  1. the Backend Server is queried to know all the own photos using the uid (not shown in the animation);
  2. a PUT or DELETE method is used to edit or delete the photo and its attributes;
  3. after the editing or deletion, all the Backend Server instances are notified of the change;
  4. all clients connected to the SSE subscription are notified of the change.

SRE thoughts

In this section I want to reason about the design in terms of scalability, reliability and efficiency.

Scalability

If we think to increase the number of users, the long polling connection (used for SSE) will immediately saturate the Backend Server. In fact, every long polling connection keeps busy a process or a thread. In my example configuration I setup just 8 processes (mono-thread) for container. This means the system can serve 7 concurrent users without problems: 1 connection is needed to perform queries and uploads. To serve more people, I need to spin up more containers. The design allows us to scale horizontally the Backend Server. However, this is a strict bottleneck and it comes from the fact I wanted to keep my clients totally synchronous with the server. Using an asynchronous approach can reduce resource depletion. In other words, if the client regularly polls the server we don’t need to keep a connection up (and consequently a dedicated thread or process).

If we think our users increase the image size or the number of images we can face a CPU shortage and a disk shortage.

CPU exhaustion can lead to higher response time. To avoid this higher response time we can think to decouple the image processing from the request processing. Here a message server (e.g. MQ) or a kind of queue can be useful to decouple them. Obviously we can also scale horizontally adding another Backend Server instance.

For disk shortage, there is little to do, we need to add disks or think to use a better compression algorithm (e.g. webp).

Another point can be the static content. The Frontend Server and Image Server serve, in the end, static content. We could move this content to a Content Delivery Network (CDN) in order to offload and be closer to the users.

Keep in mind, to be conscious of the next best action we need more observability. For example, tracking which is the slowest query from the client perspective can suggest us where to improve our system.

Reliability

All the components are containers, this eases the deployment. In fact I deployed the system on my simple DockerLab infrastructure. The containers I developed (Frontend Server, Backend Server and Image Server servers) are stateless, therefore it is possible to adopt an active/active redundancy without coping with session affinity.

The pain points are the disk space where the photos are uploaded and the database (at this point of writing is a simple SQLite, so a file).

Unfortunately, in my DockerLab infrastructure to share a disk I just use NFS exported by the router (that is already a single point of failure). In a more complex infrastructure, the data (i.e. the photos) should be replicated in different disks. Then, a problem about synchronization will arise.

Speaking of the database, at the moment has the same shared fate of the photos, it is a simple file along them. To create more replicas and maybe a better performance, I was thinking to introduce MongoDB with a single-leader configuration. Every MongoDB instance should have its own not-shared disk, but using an algorithm to spread the data.

Efficiency

Speaking of efficiency. I think we can save a process (or a container) using the same server for Frontend Server and Image Server, in the end they are both static content servers. Or, as I said, we can evolve it to a CDN.

Conclusion

Like other projects that I have done, this system could be written in a simpler way, especially if we think it will be used just by a bunch of people. I am not saying the design is ready for production, rather it is an experiment trying to understand what are the weaknesses and strengths of the used approaches.

Designing and questioning the design is the part that I love.

--

--

--

Site Reliability Engineer at Google. This is my personal blog, thoughts and opinions belong solely to me and not to my employer.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Build a Chat system using Rails 5 API Action Cable and ReactJS with multiple private rooms and…

Conway’s Game of Life in JavaScript

JavaScript vs Java: Know The Major Differences

411 Interview Questions #7

NextJs and Server-Side Rendering SSR

Sorting table dates in a Vue CLI project

FAQs about Ember.js in 2019

Run Node apps in Android devices!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Fabrizio Waldner

Fabrizio Waldner

Site Reliability Engineer at Google. This is my personal blog, thoughts and opinions belong solely to me and not to my employer.

More from Medium

How to write a custom adaptive authentication script in WSO2 Identity Server 5.11.0

A story of leaking uninitialized memory from Fastly

What is Memory Leak, Valgrind and PID?

Creating a Subscription Bot for Gym Classes