Row expired when loading Anvil App from APIs

Your time measurements only look at a search and don’t use the result of the search.

If you try to use just a copule of rows, you see that server calls are already twice as fast, regardless of whether you use accelerated tables or keep server running.

This test shows exactly what I was talking about: row objects and table iterators are very well optimized, and they often do a good job at guessing what you need. But as soon as you need something slightly different from their guesses, their performances can kill your app and be orders of magnitude slower than server calls.

Here I have added my version of server call, where I return dictionaries instead of row objects, and you can see that with just 2 rows we are already twice as fast:

no accelerated tables - no keep server running
Client took 3.200999975204468 Seconds
Server took 4.150000095367432 Seconds
Server2 took 1.480000019073486 Seconds
Client took 0.2909998893737793 Seconds
Server took 0.5590000152587891 Seconds
Server2 took 0.3329999446868896 Seconds

no accelerated tables - yes keep server running
Client took 3.216000080108643 Seconds
Server took 3.745999813079834 Seconds
Server2 took 1.233000040054321 Seconds
Client took 0.5090000629425049 Seconds
Server took 0.4000000953674316 Seconds
Server2 took 0.4110000133514404 Seconds

yes accelerated tables - no keep server running
Client took 2.898000001907349 Seconds
Server took 3.594000101089478 Seconds
Server2 took 1.364000082015991 Seconds
Client took 0.1559998989105225 Seconds
Server took 0.3919999599456787 Seconds
Server2 took 0.4040000438690186 Seconds

yes accelerated tables - yes keep server running
Client took 3.368000030517578 Seconds
Server took 3.846000194549561 Seconds
Server2 took 1.38100004196167 Seconds
Client took 0.2170000076293945 Seconds
Server took 0.6310000419616699 Seconds
Server2 took 0.4000000953674316 Seconds

Here is the server code:

@anvil.server.callable
def return_rows():
    return app_tables.table_1.search()

@anvil.server.callable
def return_row():
    return app_tables.table_1.get(someid=1)

@anvil.server.callable
def return_rows2():
    return [dict(row) for row in app_tables.table_1.search()[2:3]]

@anvil.server.callable
def return_row2():
    return dict(app_tables.table_1.get(someid=1))

And here is the client code:

    def form_show(self, **event_args):
        summary = []
        
        start=time.time()
        rows=app_tables.table_1.search()
        x=[dict(row) for row in rows[2:3]]
        print('1==============')
        print(x)
        end=time.time()
        summary.append(f"Client took {end-start} Seconds")
        
        start=time.time()
        rows=anvil.server.call('return_rows')
        x=[dict(row) for row in rows[2:3]]
        print('2==============')
        print(x)
        end=time.time()
        summary.append(f"Server took {end-start} Seconds")
        
        start=time.time()
        rows=anvil.server.call('return_rows2')
        print('3==============')
        print(rows)
        end=time.time()
        summary.append(f"Server2 took {end-start} Seconds")
        
        start=time.time()
        row=app_tables.table_1.get(someid=1)
        x=dict(row)
        print('4==============')
        print(x)
        end=time.time()
        summary.append(f"Client took {end-start} Seconds")
        
        start=time.time()
        row=anvil.server.call('return_row')
        x=dict(row)
        print('5==============')
        print(x)
        end=time.time()
        summary.append(f"Server took {end-start} Seconds")

        start=time.time()
        row=anvil.server.call('return_row2')
        print('6==============')
        print(row)
        end=time.time()
        summary.append(f"Server2 took {end-start} Seconds")

        print('\n'.join(summary))

EDIT
If the IDE showed all the roundtrips, including the ones triggered when accessing a value in a row object when the row object already exists on the client side, but needs a roundtrip to fetch a value, one would have an idea of whether using row objects on the client is slowing down the app and should be optimized by using static dictionaries as in my example, or they don’t trigger too many roundtrips and can be used because they make the code more readable.