Skip to content

Instantly share code, notes, and snippets.

@farhad-taran
Created June 2, 2022 22:30
Show Gist options
  • Save farhad-taran/09d46d0880256983929f0d4e6f7d4ffb to your computer and use it in GitHub Desktop.
Save farhad-taran/09d46d0880256983929f0d4e6f7d4ffb to your computer and use it in GitHub Desktop.
Self managed scheduled in memory cache

I needed to come up with an in memory cache with a file back up to use in the AWS lambda environment. the storage back up uses the /tmp folder which is shared across the instances of a lambda to store the cache data, this means that the same data can be made available to different invokations and helps with cold starts. The class, first loads the file, if any data is available it tries to schedule the next refresh, otherwise it will try to refresh the empty data, the refresh function after doing its job uses the finally block to schedule the next refresh.

export type TimestampedData<T> = {
  timestamp: string;
  content: T;
};

export class FiledMemCache<T> {
  constructor(
    private readonly cacheKey: string,
    private readonly refreshInterval: number,
    private readonly dataRefresher: () => Promise<T>,
    private readonly fileStorage: FileStorageProvider<TimestampedData<T>>
  ) {
    fileStorage.get(cacheKey).then(async data => {
      if (data && data.content) {
        this.cachedData = data;
        this.scheduleRefresh();
      } else {
        await this.doRefresh();
      }
    });
  }

  private timer: NodeJS.Timer | undefined;

  private cachedData: TimestampedData<T> | undefined;

  getData(): T | undefined {
    return this.cachedData?.content;
  }

  private scheduleRefresh() {
    this.timer = setTimeout(() => this.doRefresh(), this.refreshInterval);
    if (process.env.NODE_ENV !== 'test' && typeof this.timer.unref === 'function') {
      this.timer.unref();
    }
  }

  private async doRefresh() {
    try {
      if (this.shouldRefresh()) {
        const freshData = await this.dataRefresher();
        if (freshData) {
          this.cachedData = {
            content: freshData,
            timestamp: new Date().toISOString(),
          };
          await this.fileStorage.set(this.cacheKey, this.cachedData);
        }
      }
    } finally {
      this.scheduleRefresh();
    }
  }

  private hasExpired(timestamp: string, refreshIntervalMs: number, now: Date) {
    return new Date(timestamp) <= new Date(now.getTime() - refreshIntervalMs);
  }

  private shouldRefresh() {
    return (
      this.cachedData == undefined ||
      this.cachedData.content == undefined ||
      this.cachedData.timestamp == undefined ||
      this.hasExpired(this.cachedData.timestamp, this.refreshInterval, new Date())
    );
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment