Every limit held, and one request still took 62 seconds

by•

Our free invoice generator takes a JSON post with no account behind it and hands back a PDF. So it has limits on everything.

At most a hundred line items. At most twenty taxes on any one item. A length cap on every text field, a byte cap on the logo, and checks that run before anything expensive is decoded. Each limit was chosen by asking how large one field can reasonably be, and each one was sensible on its own.

Then we measured a request that respected every single one of them: a hundred items, each carrying twenty taxes. It answered 200, and it held a worker for about 62 seconds. The same hundred items with one tax each took about 2.4.

Nothing was broken. The limits multiplied. A hundred items times twenty taxes is two thousand line and tax pairs, and each one is printed in its line and, when the names differ, again as its own row in the totals. The item limit counts items. The per item tax limit counts what sits inside one item. Nothing counted the document.

The fix was a limit on the product rather than on either factor: two hundred tax occurrences across the whole invoice, checked before the per item work it bounds. That is still a hundred lines with two taxes each, far past anything a person fills in by hand.

The same question then found two more. A tax name is not printed once, it is printed on every line that carries it and again in the totals, so it got its own cap of sixty characters instead of sharing the two hundred allowed for an item name. And the logo is drawn into a box a few centimetres wide, so a 3000 pixel image was decoded in full to paint a thumbnail, about five seconds of work against one at the 1200 pixel ceiling it has now.

The rule I keep now is to ask two questions of every limit, not one. How large can this be, and how many times does it get rendered.

Where else have you seen per field limits that were each fine and multiplied into something that was not?

12 views

Add a comment

Replies

Best

The logo example is interesting because the original file size wasn’t the only concern. I’ve seen similar problems where an apparently small output still require expensive work upstream.