How do you feel about adding built-in rate limiting, so that simultaneous calls to fetchp() cannot exceed a configured limit, but will be queued instead?
Actually I want a clientside backoff for normal serverless fetch calls too, so that kind of functionality should be a wrapper. Maybe we can use https://github.com/jonbern/fetch-retry
We could build on it, but would probably still need a central queue. When multiple requests are spammed at the API, then even a delayed retry won't be enough. Perhaps a wrapper function funnel() that chains async callbacks?
* "chains" as in wait for the previous promise to settle
There's https://github.com/ricokahler/pool
Why are you using that exponential backoff instead of queuing requests on error and waiting the delay that's reported in the "retry-after" response header on 429?
Did you change comments?
There are 429 generated in a few different places (in webcode and by cloud run). Not sure if the Google 429 has a retry-after. Anyway, the webcode rate limit is very low for legacy reasons. It used to be that a request to webcode caused a request to observablehq on a 1-to-1 basis. But since the change that led to "2020-10-01 fetchp updated to take advantage of page reuse for much faster warm latencies" webcode keeps pages warm so does not do many upstream requests anymore
My next task on the backlog is to redo the rate limits to reflect the recent architectural changes. They were there to prevent me DoSing Observablehq but that won't happen for endpoints using the reusable flag, so rate limits can be dropped in those cases. (will try to do this week)
> Did you change comments?
Yup, removed my previous comment after I noticed that retries are in fact handled. (Sorry for the noise!)
> Not sure if the Google 429 has a retry-after
I see the the header in the Google responses. For a simple repro run `Array(20).fill('https://github.com').map(fetchp)`.
> will try to do this week
No pressure. At the moment it mostly seems to be a DX issue (due to a possibly large number of failed requests in the log) without practical implications.
It definitely affects performance. There is a 1Hz rate limit you cannot get around. Anyway, I have just lifted it to 10Hz with 100 request bursts. See if it works better for you now.
I found that bursts may also trigger DDOS protection on npmjs.com, so I'm keeping the current implementation (fetching resources on after another instead of simultaneously). Thanks for the update though!
What are your thoughts on a "bundling mode", where multiple requests can be made through a single call to the proxy?
E.g., if I wanted to fetch multiple resources, I'd pass in an array of URLs (or array of arrays, each having [url, options])) and would get the results returned in order. This would allow circumventing rate limits of the proxy (assuming there are no rate limits for outside requests made by the proxy itself).
On a published notebook, I am getting: "Deployment 'proxy_82cbcff48d' not found, did you remember to publish your notebook, or is your deploy function slow?"
Does this require logging into webcode.run first?
no, infact this does not use webcode. It is serverless-cells only which does not have a login.
Do the tests run for you?
https://observablehq.com/@tomlarkworthy/fetchp-tests
fetchp works by puppeteer opening the notebook from its public URL. If you do anything dynamic so the proxy cell does not run on notebook open then it won't work. Check in anonymous mode
The tests run OK if I run the fetchp-tests notebook anonymously. Interestingly, when I open the tests notebook in a regular tab, and then run the test notebook incognito in the background (after refreshing), I get a "Error: expect(received).toBe(expected) // Object.is equality Expected: 200 Received: 403" for all the four 'Inject' tests.