inventid / pedeffy

Generate any PDF file from a template. Within a microservice. Using React.
MIT License
25 stars 4 forks source link

Response is blocked by pdf generation in case of multiple large requests which result in larger PDFs #16

Open kissthom opened 3 years ago

kissthom commented 3 years ago

First of all I'd like to say a big thank you for this library, it is a really awesome example how to use react-pdf on node!

I ran into some difficulties and I really hope you can help me out.

Generating larger PDF-s (> 30 pages) can take some time. For my use case it takes ~8 seconds right now. If I call the pdf generating endpoint only once without concurrent requests it works like a charm. But If I call it concurrently with requests that trigger generating these larger pdf-s that I can run into problems.

Use Case:

  1. I call my endpoint with request 1 and request 2 directly after each other (both are large json-s triggering the generation of large PDF-s)
  2. Request 1 arrives in request handler
  3. PDF will be generated for Request 1 (~8 sec)
  4. Piping PDF into response begins: first chunk gets written, response partially returned to client (status code...first chunk)
  5. Request 2 arrives in request handler
  6. PDF will be generated for Request 2 (~8 sec) (Client's first request is still blocked, still waiting for the whole response)
  7. Piping PDF into response begins
  8. Piping for both PDF-s are done, both responses for returned (probably chunks after chunks but it is not relevant for the example)

Although the first PDF was generated in 8 seconds it could be returned after step 4 but unfortunately handling request 2 with long running PDF generation kicks in and blocks the writing of first response. As a result first request takes ~16 seconds, second request 8 seconds to complete.

How I see it it can cause 2 problems:

  1. Clients can run into timeouts if multiple large PDF-s are getting generated after each other
  2. If the server generates these large PDF after each other and cannot return the whole responses for them at once then the readable streams (I guess) can pile up in memory ending in consuming all the available memory which results in this error I guess:
Thu Jul 23 2020 15:23:53 GMT+0200 (Central European Summer Time) error: Error occurred while rendering: "abort("Cannot enlarge memory arrays. Either (1) compile with  -s TOTAL_MEMORY=X  with X higher than the current value 134217728, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ") at Error
    at jsStackTrace (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:443:15)
    at stackTrace (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:453:14)
    at abort (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:9808:182)
    at abortOnCannotGrowMemory (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:457:5)
    at enlargeMemory (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:459:5)
    at FC (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:9628:49)
    at oB (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:8461:17)
    at qC (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:9500:13)
    at sf (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:3287:31)
    at Kq (/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:5995:87)
If this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information."

How could I tackle these problems? I'd really like if the long running PDF generation would not block the returning of the previous response(s). Any idea, suggestion?

rafalkrupinski commented 3 weeks ago

Did you firgure it out?

Generally node is single threaded (IO may be offloaded to separate threads, not CPU bound code). You'd need some sort of workers

rogierslag commented 2 weeks ago

I've tried some approaches with eg piscina, but ran into other issues with those unfortunately. So far I'd recommend spinning up multiple containers if require