While my global is quite simple, and it's size is about 1.1 GB, or around 18 bytes per record

USER>do ##class(%GlobalEdit).GetGlobalSize("/usr/irissys/mgr/user","YYY",.all,.used)

USER>zw all

USER>w all*1024*1024/65000000

After restarting, with a cold cache, I got 17 seconds

USER>s sub="", count = 0, ts = $zh for { set sub = $Order(^YYY(sub)) quit:sub=""  set count = count + 1 } write !,"elapsed: ", $zh-ts 

elapsed: 16.994676

So, my disk can read around 65MB per sec

If your enterprise still uses mechanical disks, that's still a factor that has to be considered

All the blocks required to be read to get through the list can be placed all around the disk/s. And it takes time. On a live system, with many changes, and when a lot of different data is stored, the next block can be far from the previous one. So, on mechanical discs, defragmentation is matter, and may slow the speed.

I don't know the character of your data, but if you have a lot of data, stored in the globals, it will require to read much more blocks, to even just count the items.

And most probably the easiest way to solve it, is just to use bitmap index.

110 minutes, it seems impossible, or, it's something way too wrong

USER>set ts = $zh f i=1:1:65000000 { set ^YYY(i)="somedata" } write !,"elapsed: ", $zh-ts 

elapsed: 11.126631

USER>s sub="", count = 0, ts = $zh for { set sub = $Order(^YYY(sub)) quit:sub=""  set count = count + 1 } write !,"elapsed: ", $zh-ts 

elapsed: 9.549079

Here result in seconds

Yes, for sure, my example is too simple, and too far from any real situation. 

And there are multiple issues that may happen with your data, it can be how it is stored, where it is stored, how much data in values. And it's difficult to suggest how to check it

Have a look at my series of articles about globals in the database, just for information, it may help understand something, what may go wrong

In any case, there is a right way to count objects, without counting all of them this way. Is using bitmap index, which you can use even if you have own storage, and do not use objects yet. You still able to build own bitmap index, and count items by this index will be at least 64000 times faster, whereas 64000 is just chunk size for bitmap, and speed will vary depends if you don't have much empty spaces between id's, which needs to be numeric

Python may help

Class dc.Demo

ClassMethod ValidateJSON(data As %String = "") As %Status [ Language = python ]
import iris
import json
from json import JSONDecodeError

    return iris.system.Status.OK()
except JSONDecodeError as ex:
    return iris.system.Status.Error(5001, f"{ex.msg}: line {ex.lineno} column {ex.colno} (char {ex.pos})")
except Exception as ex:
    return iris.system.Status.Error(5001, repr(ex))


And result

USER>set status = ##class(dc.Demo).ValidateJSON("{""aa"":123 ""name"": ""value""}") do:'status $system.OBJ.DisplayError(status) 
ERROR #5001: Expecting ',' delimiter: line 1 column 11 (char 10)

USER>set status = ##class(dc.Demo).ValidateJSON("{""aa"": true, ") do:'status $system.OBJ.DisplayError(status) 
ERROR #5001: Expecting property name enclosed in double quotes: line 1 column 14 (char 13)

USER>set status = ##class(dc.Demo).ValidateJSON("{""aa"": wrong ") do:'status $system.OBJ.DisplayError(status) 
ERROR #5001: Expecting value: line 1 column 8 (char 7)

USER>set status = ##class(dc.Demo).ValidateJSON("{""aa"": true}") do:'status $system.OBJ.DisplayError(status)

Not so much magic in it, if you already know how to read it, the content of XData is just a Stream, which can be used both ways. Just Write to it, and %Save it. And you will probably need to compile the class

Class Demo.Test

XData OpenAPI [ MimeType = application/json ]

ClassMethod Update()
    set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen($ClassName(), "OpenAPI")
    do xdata.Data.Write("{}")
    do xdata.%Save()


In VSCode, the content of XData will not appear after that, because it happened on the server, and VSCode will not see the changes, you'll need to export it manually

I've published online demo, where you could play with two executions of FHIR Load for two different patients.

This uncovers some interesting discoveries, like, about 40% of the time is spent on IndexResource

And about 20% to ValidateResource 

Of course, this can't be very accurate, but can help to understand what actually happens during the process, and at what moment. Including finding places, where it's not just reads of globals, but sets or even kills. Something like, 17% of GloKills are happening during CommitTransactionBundle

While the requests is ended up in IRIS, than IRIS is responsible for the response, and it should support responses in HTTP/2 which is very different from HTTP/1. In HTTP/1 it does not matter how many connections are, queries will be processed one by one, and responses will go accordingly one by one. But in HTTP/2 queries processed simultaneously and response will go to the client as soon as it's done, no matter where it's started and it goes through the same connection, while in HTTP/1 connection per request. And IRIS and for sure Caché does not have support for it, the only way it's working is to process one request per connection and response as soon as the result is ready.

So, even if you manage to mimic HTTP/2 somehow, it will not help almost at all, the queries in the connection still process synchronously.