jaredwray / keyv

Simple key-value storage with support for multiple backends
https://keyv.org
MIT License
2.62k stars 145 forks source link

Prefix filter for Iterator #1181

Closed MarZab closed 6 days ago

MarZab commented 1 week ago

I am trying to implement a prefix scan and delete. While a generalised search was ruled out years ago, I would argue the iterator returning all values is not viable for large caches.

My proposal is to add a prefix filter option to iterator (any maybe clear) - that would be implemented as a per-invocation extension of the namespace.

Here is my use-case (albeit using a non backward-compatible signature, for ilustrative purposes):


export class KeyvSessionService {

  // get all user active sessions
  async list(userId: string): Promise<Array<[string, Session]>> {
    if (!this.keyv.iterator) {
      throw new Error('Provider does not support iterator');
    }
    const sessions: Array<[string, Session]> = [];
    for await (const [key, value] of this.keyv.iterator({ prefix: userId })) {
      sessions.push([key, value]);
    }
    return sessions;
  }

  // remove all user sessions
  async clear(userId: string): Promise<void> {
    await this.keyv.delete((await this.list(userId)).map(([key]) => key));

    // this would be cleanest, providers could decide the best way to implement this
    // return keyv.clear({ prefix: userId });
  }

  // crud
  keyv: Keyv<Session>;
  constructor(private readonly store: KeyvStoreAdapter) {this.keyv = new Keyv({ store: this.store, namespace: 'session' }); }
  set(userId: string, sessionId: string, session: Session): Promise<boolean> {return this.keyv.set(`${userId}:${sessionId}`, session);}
  get(userId: string, sessionId: string): Promise<Session | undefined> {return this.keyv.get(`${userId}:${sessionId}`);}
  delete(userId: string, sessionId: string): Promise<boolean> {return this.keyv.delete(`${userId}:${sessionId}`);}
}

I would be happy to contribute to this cause.


Note: I explored using a new Keyv({ store: this.store, namespace: 'session:${userId}' }); per invocation but creating/destroying has overhead/side effects that make this pattern unviable in my mind.

jaredwray commented 6 days ago

Hi @MarZab - currently we do not plan to offer a feature around search / scan. One thing to note is that if you are using an in-memory store it will be very high performant. One thing to think about is using Cacheable so that you can do in-memory and also layer 2 / secondary would be your persistent store to keep performance.

Still confused as to why you need an entire store namespace per user session as usually you would want to just make the key be the {userId}.