[ 
https://issues.apache.org/jira/browse/UNOMI-1016?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Serge Huber updated UNOMI-1016:
-------------------------------
    Affects Version/s: unomi-4.0.0
          Description: 
h3. Summary

Every request to a public endpoint (context.json, eventcollector, client) looks 
up its tenant by API key with {{TenantServiceImpl.getTenantByApiKey}}. That 
method loads every tenant from the search engine 
({{persistenceService.getAllItems(Tenant.class)}}, which is a scroll query over 
all tenant documents), deserializes them, and compares the key against each 
tenant's key hashes. The GraphQL endpoint does the same for public keys and for 
private keys.

The cost grows with the number of tenants and is paid on every tracked page 
view and event. On a server where tenants are created at runtime, with hundreds 
or thousands of tenants, this becomes the dominant per-request cost, and it 
opens a scroll context in the search engine for each request.

h3. Where
* {{TenantServiceImpl.getTenantByApiKey(String, ApiKeyType)}}: 
{{executeAsSystem(() -> persistenceService.getAllItems(Tenant.class))}}, then a 
linear scan.
* Callers: {{AuthenticationFilter}} (public paths), 
{{GraphQLServletSecurityValidator}} (public and private keys).
* Key hashes are SHA-256 ({{SecretHashServiceImpl}}), so the comparison itself 
is cheap; the cost is the query and the deserialization.

h3. Ways to explore
* Keep an in-memory index from key hash to tenant id, rebuilt when tenants or 
keys change (the same snapshot approach UNOMI-1013 uses for tenant origins: 
loaded at start, updated on save, refreshed periodically and on a miss, 
rate-limited).
* Or query by key hash: store the hash in an indexed field and look it up with 
a term query instead of loading every tenant. Revoked and expired keys must 
still be refused.
* Either way: a key that was just generated or revoked must take effect 
promptly on all nodes; document the propagation delay.

h3. How to test
* A public request with a valid key does not load all tenants (unit test on the 
lookup, counting persistence calls).
* A newly generated key works on the next request; a revoked or expired key is 
refused.
* Measure request latency with, for example, 1,000 tenants before and after.

  was:
h3. Summary

On each public request that carries an API key, Unomi finds the matching tenant 
by loading every tenant from storage and checking their keys one by one. On a 
server with many tenants, that work repeats on every such request and can 
become a large share of the cost of serving public traffic.

The goal of this ticket is to make tenant lookup by API key cheaper, so public 
requests do not pay for a full scan of all tenants each time.

This is separate from the per-tenant website origin work (UNOMI-1013). It is 
about the shared cost of resolving which tenant owns a key.

h3. Ways to explore
* Keep an in-memory map from key to tenant, refreshed when tenants are created, 
updated, or removed.
* Store a durable index that can resolve a key without loading every tenant 
document.
* Measure the cost with a realistic number of tenants so the change can be 
checked under load.

h3. How to test
* Unit and integration tests that resolve a tenant by public and private key 
still pass.
* With many tenants, looking up a key no longer loads every tenant from storage 
on each call.
* Creating, updating, or deleting a tenant still makes the new keys work and 
the old keys stop working.

              Summary: Public API key lookup reads every tenant on each request 
 (was: Speed up looking up a tenant by its API key)

> Public API key lookup reads every tenant on each request
> --------------------------------------------------------
>
>                 Key: UNOMI-1016
>                 URL: https://issues.apache.org/jira/browse/UNOMI-1016
>             Project: Apache Unomi
>          Issue Type: Improvement
>    Affects Versions: unomi-4.0.0
>            Reporter: Serge Huber
>            Priority: Major
>             Fix For: unomi-4.1.0
>
>
> h3. Summary
> Every request to a public endpoint (context.json, eventcollector, client) 
> looks up its tenant by API key with {{TenantServiceImpl.getTenantByApiKey}}. 
> That method loads every tenant from the search engine 
> ({{persistenceService.getAllItems(Tenant.class)}}, which is a scroll query 
> over all tenant documents), deserializes them, and compares the key against 
> each tenant's key hashes. The GraphQL endpoint does the same for public keys 
> and for private keys.
> The cost grows with the number of tenants and is paid on every tracked page 
> view and event. On a server where tenants are created at runtime, with 
> hundreds or thousands of tenants, this becomes the dominant per-request cost, 
> and it opens a scroll context in the search engine for each request.
> h3. Where
> * {{TenantServiceImpl.getTenantByApiKey(String, ApiKeyType)}}: 
> {{executeAsSystem(() -> persistenceService.getAllItems(Tenant.class))}}, then 
> a linear scan.
> * Callers: {{AuthenticationFilter}} (public paths), 
> {{GraphQLServletSecurityValidator}} (public and private keys).
> * Key hashes are SHA-256 ({{SecretHashServiceImpl}}), so the comparison 
> itself is cheap; the cost is the query and the deserialization.
> h3. Ways to explore
> * Keep an in-memory index from key hash to tenant id, rebuilt when tenants or 
> keys change (the same snapshot approach UNOMI-1013 uses for tenant origins: 
> loaded at start, updated on save, refreshed periodically and on a miss, 
> rate-limited).
> * Or query by key hash: store the hash in an indexed field and look it up 
> with a term query instead of loading every tenant. Revoked and expired keys 
> must still be refused.
> * Either way: a key that was just generated or revoked must take effect 
> promptly on all nodes; document the propagation delay.
> h3. How to test
> * A public request with a valid key does not load all tenants (unit test on 
> the lookup, counting persistence calls).
> * A newly generated key works on the next request; a revoked or expired key 
> is refused.
> * Measure request latency with, for example, 1,000 tenants before and after.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to