What is wrong with plone.scale?

Maybe we can make this configurable so that you can keep generating scales on the fly. In the end, it is a tradeoff between the current way of image handling and what Asko proposes.

pros (status quo):

  • fast upload (no heavy computation necessary)
  • spare HD space
  • you can change the scales in the settings without having to re-calculate

cons (status quo):

  • slow first request on images which require a warmup script to get a half-decent performance on websites with image-heavy composite pages (no matter if Plone or Volto)

  • from a REST API perspective writing to the database on a GET request violates basic HTTP design principles as well as REST API best practices (GET requests should always be idempotent and should never modify a resource). I understand that for scales this is debatable but I was really shocked TBH when I saw the logs with database writes on GET requests

  • not sure about his but how is it possible to have a read-only instance running when scales write to the database?

Asko already gave some hints on how we could ensure fast image processing, HD space shouldn't be an issue in 2020. I agree that recreating the image scales when you change the images in the control panel is indeed a nice feature.

Though, a tradeoff includes comparing this nice feature with the downsides which are loading times and scalability. Static site generators are kicking our asses when it comes to image optimization and delivery speed. Have a quick look at what Gatsby does with images and this blows your mind. We are lightyears behind this. Page speed is not a nice feature these days but a base requirement for any webpage.

Plone does not offer any modern image formats such as WebP or JPEG2000, which take some time to be generated. We do not offer srcset (expect for retina images). Imagine if Plone has to generate WebP, JPEG2000 and JPEGXR images together with a srcset on the fly and then deliver multiple images on-demand with the current write on read approach.

We are not talking about future nice-to-have features here but features that are some kind of standard on modern websites and features that other CMSes already offer out of the box today (Wordpress, Neos and most likely others as well).

Asko convinced me that this is a problem that we should try to solve for Plone and within Plone before we evaluate third-party solutions. PIL can do lots of things with images, including generating modern formats and Plone and Volto can deliver them, so we do not necessarily need a third-party image server.

Maybe we can schedule a call on Monday to discuss this in a hangout since I'd really like us to work on this next week and push this further.

2 Likes

IMO image scales should only be generated on demand. The write on read is happening very seldom, if the caching and invalidation strategy is right and @fredvd bug is fixed (multiple blobs for the same scale). That's saving a lot of space and processing power since you never need all the scales of every image.

@sneridagh what if you return the URL to a to-be-scaled image when no cached scale is available (like: https://volto.kitconcept.com/api/test/slider_pyranometer.jpg/@@images/image/large) and the URL to a generated scale once one is available (like your referenced: https://volto.kitconcept.com/api/test/slider_pyranometer.jpg/@@images/17588e0c-df0a-4c02-9376-904282359f19.jpeg)?
With your approach you need the scales beforehand and that doesn't seem right.

I would not count that as bug. Fixing that would add much more complexity than justified by the saved disk space.

Except for responsive images, you need to have all the possible scales available at any time.

You only need them if they are accessed.

There are for sure use cases where not all scales are needed. E.g. for a intranet you probably need much less often a 4xDPI phone scale. Also there might be content like old image galleries which are there for reference but very seldom accessed or downloaded in high resolution.

If your use case really enforces to generate scales on upload, why not extend plone.image why not making this an configuration option? Or a marker interface / behavior for certain types of images which need scale generation on upload?

But when those dozens of 4xDPI phone scales on landing page are accessed at the same time for the first time, it is already too late to generate them.

If those are not really required, "High pixel density mode" is left disabled and unnecessary large image scales are removed from the site configuration completely.

To make the scaling framework simpler, not even more complicated than it is.

I care that they are blobs. so then they work with collective.xsendfile. They currently don't as they aren't blobs.

:eyes: Merge pull request #12 from collective/frapell-image-scales · collective/collective.xsendfile@e6f4d51 · GitHub

There is one other option other than write-on-read or "generate them all on upload". You could redirect to a temporary image (or the original) on first access (with random url so it doesn't get cached) and kick off an asynchronous process to generate the correct scale. Only the first access will look weird. But I agree that generating them in advance would be the more sensible default.

2 Likes

Just for the record. plone.restapi was generating the @@images/image/large URLs and we changed it to use the UID-based-image-scales to ensure that caches were updated after an image was updated:

2 Likes

This was the right thing to do. :+1:

I agree. The first access could return temporary non-cacheable redirect to the closest available scale or the original when no scale has yet been generated. For REST API it would be enough that the keys and dimensions for the upcoming scales are available.

But since we don't have built-in async solution, sync (parallel) generation must be the default. Yet, this does not prevent making it async later.

1 Like

Unorthodox idea for optional async scale generation for the most usual deployment scenario of ZEO instances with shared blobs:

  • the initial scaling only creates empty stubs resulting empty blob files (=as fast initial write as possible)
  • as long as blobs are zero byte, non-cacheable temporary redirect is made to best available scale
  • meanwhile a worker thread is creating scales from a memory queue, working outside ZODB, writing directly to blob files, replacing them (queue and worker similar to cache purging worker or mailhost queue worker)
  • once scale files are detected to be more than 0 bytes, they get served normally.

AFAIK, this should work with most WSGI deployments. Obviously would not work without "shared blobs" configuration. And there are probably some plot holes I cannot foresee.

(All that said, I should first try if simple synchronous multiprocessing based parallel scale generation is good enough.)

One more note: If an async scale worker would know the exact ZODB OIDs for placing the scales, writes could be done with proper ZODB connection and transaction. There is no need to do traversing or access catalog, so even a fresh ZODB connection should be fast.

The more I think about this option, the more tempting it gets. Usually I've been very careful about accessing ZODB directly in Plone context, but this sounds simple enough to work that way. Dedicated connection for this worker thread only needs access to the scaled image and the annotation attribute (OOBTree) of the CMF content. So it needs to wake up only minimum amount of objects, possibly with direct OID access, and very small connection cache is enough.

Wow, what a thread. Difficult to keep all in mind here. But a simplified version of my thought:

Overall I am a big fan of off-loading the problem to Thumbor (or similar) for larger sites, while keeping the image handling with a subset of possible features (probably the current ones) inside Plone for smaller/less image loaded sites.

My main intend is, that we should not reinvent the wheel.

Thumbor is very pluggable, so a providing a plugin fetching the original source image from ZODB or via restapi once is possible by its architecture.
The scale then can be stored in FS or elsewhere (this is part pluggable as well).
With the many possible plugins things like manual or auto-focal point, image enhancement, ... can be done inside Thumbor. Plone then needs to generate a signed URL (shared secret) with all parameters for Thumbor.

1 Like

I agree on not reinventing the wheel.

Yet, because we already have the wheel, I can patch it to fix the current production issue, buying more time for all the changes required for thumbor.

1 Like

I don't see this as either or decision. We could try to push image scaling inside Plone as far as we can and as well write an integration for Thumbor. Then everybody can decide what's the best option for their particular use case.

I think there are lots of similarities to the ZCatalog/Solr/ES situation in Plone. The ZCatalog covers the basic indexing/search needs. If you need more sophisticated search capabilities and to scale, use Solr/ES. Though, I would never lightly recommend to use an external solution because it always comes with a cost both for the integration and as well for running it in production.

Image scaling, srcset and modern image formats (WebP, JPEG2000/XR) are, in my opinion, something that Plone should support out-of-the-box (both classic Plone and Volto), like the ZCatalog supports basic indexing/search requirements. Focal point recognition and other more advanced techniques might be easier to do in external solutions.

TBH I don't know yet which option I'd personally prefer. Both have their pros and cons and we will only see this when we give it a shot.

I learned from past experiences that if @datakurre has time and is willing to spend time on it, something good will come out of this for sure. So I am looking forward to see how far he can push it. The same is true for an external service like Thumbor, if someone is willing to work on this I'd love to see this happening. :slight_smile:

BTW: I set up some performance tests to see how much time it takes to generate images with write-on-read. I will try to find time to continue to work on this during the sprint week...

Btw, focal point is not magic. It needs just an x and y coordinate stored somewhere. Then it could be made even without a new scale just with modern object-fit and object-position CSS.

2 Likes

Talking about writes on read it would be interesting to discriminate when this happens.

You can check the list of transaction in the ZODB with the fsdump utility:

./bin/fsdump var/filestorage/Data.fs > dump.log

Example output

Trans #12345 tid=012345668abcd time=2020-04-21 15:33:00.386625 offset=666
    status='whatever' user=b'whatever' description=b'whatever'
  data #00000 oid=000000000002132a size=945 class=BTrees.OOBTree.OOBucket
  data #00001 oid=000000000dsdafc size=2312 class=BTrees.OOBTree.OOBucket

To get the object written to the DB from the oid do in a instance debug session:

>>> from ZODB.utils import p64
>>> app._p_jar[p64(int(oid, 16))]

This should tell you:

  1. what is written in the DB
  2. when it is written

CC @tisto @rodfersou

1 Like

I did not notice this issue with plone scales before but looking at Change to use field value _p_mtime instead of context object _p_mtime… by datakurre · Pull Request #91 · plone/plone.namedfile · GitHub it appears evident now! Thanks for fixing this!

1 Like

Another thing about keys are that they may still expire in a day. For example:

  • update image
  • the usual scales are created on-demand immediately
  • ...
  • after a day some use case requires a new scale not yet generated
  • cleanup happens and all the scales created yesterday are lost, even they would still be good