You should check these:
Is searching data stored in event logs prohibitively slow?
More specifically, what is the time complexity of eth_getLogs?
In worst case, where every block contains log matching your query it is 0(n). But it's rarely a case. Bloom filters utilize probability of false positives, so the more sophisticated your filter is (more topics it has), the faster you will get your results.
How does Ethereum make use of bloom filters?
When a block is generated or verified, the address of any logging contract, and all the indexed fields from the logs generated by executing those transactions are added to a bloom filter, which is included in the block header. The actual logs are not included in the block data, to save space.
When an application wants to find all the log entries from a given contract, or with specific indexed fields (or both), the node can quickly scan over the header of each block, checking the bloom filter to see if it may contain relevant logs. If it does, the node re-executes the transactions from that block, regenerating the logs, and returning the relevant ones to the application.
About the bloom filter:
A Bloom filter is a space-efficient probabilistic data structure [...] that is used to test whether an element is a member of a set. False positive matches are possible, but false negatives are not.
For the case you want to check this in the code, this is a good starting point: https://github.com/ethereum/go-ethereum/blob/master/eth/filters/filter.go
Also interesting: What is the capacity of the logsBloom?