Suppose a user defines input arrays on a JavaScript frontend. These are sent to a Python backend via the REST API. The backend computes a result for every combination of each array's elements. It then replies with a JSON file containing the results.

Here's a toy example with two inputs. The first has five types of oranges, and the second, ten storage durations for apples. The backend computes a "taste index" or whatever for each input combination used in a fruit salad.

enter image description here

Question: How to decrease response times without tight frontend/backend coupling and excess complexity?

Option 0: We could have requests with 5 types and 10 durations, ie, 15 elements in total. The response would contain a 50 by 3 matrix listing each input/output combination, so 150 elements in total. Here's the start of this matrix:

enter image description here

In my actual app this approach is painfully slow due to bandwidth constraints: ie, large data volumes being sent. The app has 5-8 inputs with 2-1000 elements each. Each input element can be a string, number or a large data structure, whereas results are always double-precision numbers. It's an Angular 9/Electron Desktop app with a Python 3.8 Flask/Celery parallelised backend.

Option 1 Requests in my current solution are as above but a reply only contains a list of 50 results. That is, I only send the Result column from the above table. Indeed, suppose the backend computes the results in nested loops like so:

results = [ ]
for orange in oranges:
    for apple in apples:
        results.append(compute(apple, orange))

Then the frontend could map the index of each result back to the original input levels, eg, to produce a plot. So there's no need to replicate the inputs in an HTTP response.

This approach has reduced the response times between 5 and 20 folds, depending on the problem size. However, it does bring extra coupling between the frontend and the backend. The frontend now needs to know the exact order in which the backend processed the inputs. This also constrains backend implementations, eg by requiring one big multi-loop method instead of several smaller methods. It's also proven error-prone in use-cases with many inputs.

Could there be another solution, somewhat less performant but with a cleaner design?

I've also tried splitting up input array between requests so as to reduce the load per request/response cycle. However this appeared too complicated: need to re-assemble results, and so on. My internet search has found only remotely related references, such as this SE question, although I might have used the wrong search terms.

  • 5
    its unclear what the problem is. is the response slow due to its size vs bandwidth, or due to cpu constraints on the back end? why is one data format smaller than the other? – Ewan Aug 29 at 21:50
  • 1
    why not return the data as multi dimentioned array as shown in the picture – Ewan Aug 29 at 21:54
  • how does the front end plot 8 dimensional data? – Ewan Aug 29 at 21:56
  • @Ewan The backend computations are very fast. So it's mainly the bandwidth constraint. Sometimes it took 20-30 minutes or froze. The first format returns 150 data points in rows back to the Frontend (Apple, Orange, Result). The second format only returns 50 data points (Result.) – schrödingercöder Aug 29 at 22:04
  • 1
    You could send the 'rows' and 'columns' as a second object – Ewan Aug 29 at 22:30

May be you'd want to singe single indexing instead of two independent indices. I.e. identify each cell with a unique identifier (its values does not matter, it may be based on indices of rows and columns with a simple arithmetic transform or could be a hash, the order of cells in queries/replied then does not matter and they can be updated separately asynchronously).

In summary transform the 2D problem in a 1D problem with a single hash. Then you can decouple the work to the server and even allow multiple servers to do the job needed for each cell. This changes the nature of the problem in separate problems:

  • a single set for "types of ranges" (no need to duplicate these values, you'll eventually associate an ordered index or unorderd hash value for each)
  • a single set for "prices of apples" (same remark, transmitted separately)
  • a single set of cells (each one with its identifying hash) for computing the "taste index" (this can be any kind of computing, as you seem to just give pseudo-examples and not actual use cases)

You can then bind the client and server(s) with more freedom and optimize what needs to be synchronized with network requests (it may be useful as well for the server and client to share a common session identifier for each client that will be part of the hash built for each element of the 3 sets above, so that you can as well distribute the competing clients on the available servers, which may change dynamically or on demand).

Note that the separation between "server(s)" and "client(s)" is artificial. The server may just do nothing else than being a coordinator or communication proxy between the client(s) in a peer-to-peer system. The actual work being done only by the clients accessible via this server, and interacting asynchronously (possibly the work on each "cell" is actually performed by humans, you cannot expect that the whole "grid" of tasks will be performed in any orderly way; as well there may be restrictions/policies about how each client can interact and influence the work or result of each task or "cell" of your grid).

Finally the representation of the division of tasks in a 2D grid is possibly artificial and does not make sense (there may be spans and cells sharing their tasks or results at random places in that "grid", or the grid may just be an aggregate showing a part only of the data which may be structured completely differently (e.g. a hierarchic tree, or many more axis) and some cells may have no meaning (no task to do, no valid input, no valid result to show, no client interested on working to "fix" that).

| improve this answer | |
  • That looks like a good approach. So the server would send a request with an Oranges vector [A,B,...,D], an Apples vector [10, 20, ..., 100] and a "hash" vector, eg with indices [{0,0}, {0, 1}, ...,{4, 9}]. The response would have rows such as {0,0}, 0.12 and {0,1}, 1.38. Is that right? If not, do you mind putting a couple examples? – schrödingercöder Sep 3 at 19:20
  • that's the principle. but {0,0} could be a single unique hash, a single integer (e.g. you interleave the bits in each indice: if your index in Oranges is 16-bit and the index in Apples is 16-bit, interleaving would be 32-bit. If the indexes in Oranges and Vectors have an upper bound, you can just perform basic arithmetic (i*sizeof(Oranges)+j) which is faster than interleaving – Philippe Verdy Sep 21 at 20:53
  • it's up to you to create your suitable "hash" function – Philippe Verdy Sep 21 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.