We have been through this, and its complicated, complicated enough that a CDN is a fine solution and in practice scales us just fine.
Say I have an avatar:
sam.png
Now the client happens to need a copy of sam.png at 120x120px. How does it figure out how to get that?
Option 1:
Whenever server ships down avatar info to the client it ships an array of all the possible sizes of avatar the client may need eg:
{'sam.png' => [
[10, 'http://somewhere/png1.png'],
[20, 'http://somewhere/png2.png'],
[40, 'http://somewhere/png3.png'],
[50, 'http://somewhere/png4.png'],
[60, 'http://somewhere/png5.png'],
[70, 'http://somewhere/png6.png'],
[80, 'http://somewhere/png7.png'],
[90, 'http://somewhere/png8.png']
]
}
There are tons of locations cause with have retina vs. non retina and thus numerous device pixel ratios and multiple sizes of avatars the client may need.
Options 2:
We send a template
{ 'sam.png' => 'https://cdn.discourse.org/avatars/sam.{size}.png'}
We went with option 2, because option 1 is where madness lies. Option 2 scales super fine as our NGINX config will properly cache every avatar, and CDNS cache every avatar. So first hit warms up the CDN cache and second hit is served directly from CDN without going to the app.