Photography Project - New Changes and Infrastructure Diagram

I recently went through a process of winding down most of my AWS infrastructure in favor of a self-managed Virtual Private Server (VPS), saving myself tens of pounds a month.
The move away from Fargate to a simple containerized deployment exposed by Nginx on my VPS meant revisiting some components of the website and lead to some major improvements.
Image uploads are now async
This was a major gripe of mine with the old website. There is a lot of processing of images taking place before and after saving, using the exif data from each photo. This was a blocking process which lead to timeouts. Because of the costs and complexity of my Fargate set-up I couldn’t easily introduce a Celery or Redis component without also introducing more cost. In the new world, this process was relatively easy and completely free, I can now upload as many images at once as I like, as they are all queued and processed in parallel by Celery workers. This also triggers the pre-existing Lambdas on the S3 bucket media directory, only things have changed slightly in this area too.
Image resizing is now on-demand at the edge
Previously, a Lambda would resize each image into a couple of fixed sizes, ‘primary’ (roughly 1920px width) and ’thumbnail’ (400px). This was done at the trigger point of an image landing in the media bucket immediately after being saved by Django. It was a relatively slow process, meaning on return to the archive page the image would appear broken for 5 or 10 seconds after the upload completed, depending on the number of images being uploaded.
The upload process has now been completely overhauled, after being processed async by Celery (see above), the validating lambda simply moves the image to the public bucket and does almost no transformation other than stripping exif and trying to rescale the image to a reasonable size.
The existing CloudFront distribution now has a new Lambda@Edge which will take call to the media bucket and resize, on-demand, any url with a width=
query parameter. Once returned, this size is cached, so the process should only happen once per image, per size.
This allows images to be resized based on the device, a 400px thumbnail is an enormous waste on an iphone 13 mini for example, so I’ve dramatically reduced the payload size for every page load. This is even more important when you consider that most traffic to the website is from mobile devices.
To increase cache hits, the sizing isn’t completely dynamic and there are still some fixed sizes based on page size (consider if there are three requests for 400, 401 and 402px you would never get a cache hit), but the impact has been enormous.
flowchart TD subgraph VPS Django[Django Web Application] Redis[Redis] Postgres[PostgreSQL] Celery[Celery Workers] end subgraph AWS MediaS3[S3 Bucket: Media] PublicS3[S3 Bucket: Public] CloudFront[CloudFront Distribution] ResizeLambda[Lambda: Resize Image on Demand] ValidateLambda[Lambda: Validate, Strip EXIF] end Me[User Uploads Images] --> Django Celery --> MediaS3 Redis <--> Celery Celery --> |Update image exif|Postgres MediaS3 -->|Trigger| ValidateLambda ValidateLambda --> PublicS3 CloudFront -->|Serves optimized images| User[User Views Images] PublicS3 --> CloudFront CloudFront -->|Requests resize and caches at the edge| ResizeLambda ResizeLambda --> PublicS3 Django -->|Save image stub|Postgres Django --> |Async image processing queue|Redis
This entire end-to-end process is so fast, that if I click submit on a single image through the upload form, by the time I’m redirected to the home page, the resized image is already visible.
One or two additional changes
- CloudFlare introduced as an extra layer of security over the top of the website
- Greyscale support removed completely (slowed down image processing lambdas and only applied to about 5 photos which all looked crap anyway)
- Major refactor of Terraform
Conclusion
The performance improvements, coupled with the ridiculous cost savings of moving away from ‘Cloud’ infra and moving to a self-managed VPS are continuing to bear fruit, not just the fact that my bill is roughly 75% lower than it was for this project alone (even more when you consider it’s co-hosted and I’m doing lot MORE with the VPS), but it’s allowing me to introduce new features without having to worry about cost. It’s also way more fun to work on.