Skip to content

Commit

Permalink
feat(cache): etag caching for github GET (#26788)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
rarkins and viceice committed Jan 22, 2024
1 parent d773b5a commit 23a334c
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 6 deletions.
14 changes: 11 additions & 3 deletions lib/modules/platform/github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ import type {
import * as hostRules from '../../../util/host-rules';
import * as githubHttp from '../../../util/http/github';
import type { GithubHttpOptions } from '../../../util/http/github';
import type { HttpResponse } from '../../../util/http/types';
import type {
HttpResponse,
InternalHttpOptions,
} from '../../../util/http/types';
import { coerceObject } from '../../../util/object';
import { regEx } from '../../../util/regex';
import { sanitize } from '../../../util/sanitize';
Expand Down Expand Up @@ -316,11 +319,15 @@ export async function getRawFile(
branchOrTag?: string,
): Promise<string | null> {
const repo = repoName ?? config.repository;
const httpOptions: InternalHttpOptions = {
// Only cache response if it's from the same repo
repoCache: repo === config.repository,
};
let url = `repos/${repo}/contents/${fileName}`;
if (branchOrTag) {
url += `?ref=` + branchOrTag;
}
const res = await githubApi.getJson<{ content: string }>(url);
const res = await githubApi.getJson<{ content: string }>(url, httpOptions);
const buf = res.body.content;
const str = fromBase64(buf);
return str;
Expand Down Expand Up @@ -1220,7 +1227,7 @@ export async function getIssue(
const issueBody = (
await githubApi.getJson<{ body: string }>(
`repos/${config.parentRepo ?? config.repository}/issues/${number}`,
{ memCache: useCache },
{ memCache: useCache, repoCache: true },
)
).body.body;
return {
Expand Down Expand Up @@ -1306,6 +1313,7 @@ export async function ensureIssue({
`repos/${config.parentRepo ?? config.repository}/issues/${
issue.number
}`,
{ repoCache: true },
)
).body.body;
if (
Expand Down
1 change: 1 addition & 0 deletions lib/modules/platform/github/pr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function getPrCache(
if (pageIdx === 1 && isInitial) {
// Speed up initial fetch
opts.paginate = true;
opts.repoCache = true;
}

const perPage = isInitial ? 100 : 20;
Expand Down
8 changes: 8 additions & 0 deletions lib/util/cache/repository/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { BitbucketPrCacheData } from '../../../modules/platform/bitbucket/t
import type { GiteaPrCacheData } from '../../../modules/platform/gitea/types';
import type { RepoInitConfig } from '../../../workers/repository/init/types';
import type { PrBlockedBy } from '../../../workers/types';
import type { HttpResponse } from '../../http/types';

export interface BaseBranchCache {
sha: string; // branch commit sha
Expand Down Expand Up @@ -124,8 +125,15 @@ export interface BranchCache {
result?: string;
}

export interface HttpCache {
etag: string;
httpResponse: HttpResponse<unknown>;
timeStamp: string;
}

export interface RepoCacheData {
configFileName?: string;
httpCache?: Record<string, HttpCache>;
semanticCommits?: 'enabled' | 'disabled';
branches?: BranchCache[];
init?: RepoInitConfig;
Expand Down
31 changes: 28 additions & 3 deletions lib/util/http/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,38 @@ describe('util/http/index', () => {
},
})
.get('/')
.reply(200, '{ "test": true }');
expect(await http.getJson('http://renovate.com')).toEqual({
.reply(200, '{ "test": true }', { etag: 'abc123' });
expect(
await http.getJson('http://renovate.com', { repoCache: true }),
).toEqual({
authorization: false,
body: {
test: true,
},
headers: {},
headers: {
etag: 'abc123',
},
statusCode: 200,
});

httpMock
.scope(baseUrl, {
reqheaders: {
accept: 'application/json',
},
})
.get('/')
.reply(304, '', { etag: 'abc123' });
expect(
await http.getJson('http://renovate.com', { repoCache: true }),
).toEqual({
authorization: false,
body: {
test: true,
},
headers: {
etag: 'abc123',
},
statusCode: 200,
});
});
Expand Down
38 changes: 38 additions & 0 deletions lib/util/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { pkg } from '../../expose.cjs';
import { logger } from '../../logger';
import { ExternalHostError } from '../../types/errors/external-host-error';
import * as memCache from '../cache/memory';
import { getCache } from '../cache/repository';
import { clone } from '../clone';
import { hash } from '../hash';
import { type AsyncResult, Result } from '../result';
Expand Down Expand Up @@ -163,6 +164,8 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
httpOptions,
);

logger.trace(`HTTP request: ${options.method.toUpperCase()} ${url}`);

const etagCache =
httpOptions.etagCache && options.method === 'get'
? httpOptions.etagCache
Expand Down Expand Up @@ -210,6 +213,16 @@ export class Http<Opts extends HttpOptions = HttpOptions> {

// istanbul ignore else: no cache tests
if (!resPromise) {
if (httpOptions.repoCache) {
const cachedEtag = getCache().httpCache?.[url]?.etag;
if (cachedEtag) {
logger.debug(`Using cached etag for ${url}`);
options.headers = {
...options.headers,
'If-None-Match': cachedEtag,
};
}
}
const startTime = Date.now();
const httpTask: GotTask<T> = () => {
const queueDuration = Date.now() - startTime;
Expand Down Expand Up @@ -243,6 +256,31 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
const deepCopyNeeded = !!memCacheKey && res.statusCode !== 304;
const resCopy = copyResponse(res, deepCopyNeeded);
resCopy.authorization = !!options?.headers?.authorization;
if (httpOptions.repoCache) {
const cache = getCache();
cache.httpCache ??= {};
if (resCopy.statusCode === 200 && resCopy.headers?.etag) {
logger.debug(
`Saving response to cache: ${url} with etag ${resCopy.headers.etag}`,
);
cache.httpCache[url] = {
etag: resCopy.headers.etag,
httpResponse: copyResponse(res, deepCopyNeeded),
timeStamp: new Date().toISOString(),
};
}
if (resCopy.statusCode === 304 && cache.httpCache[url]?.httpResponse) {
logger.debug(
`Using cached response: ${url} with etag ${resCopy.headers.etag} from ${cache.httpCache[url].timeStamp}`,
);
const cacheCopy = copyResponse(
cache.httpCache[url].httpResponse,
deepCopyNeeded,
);
cacheCopy.authorization = !!options?.headers?.authorization;
return cacheCopy as HttpResponse<T>;
}
}
return resCopy;
} catch (err) {
const { abortOnError, abortIgnoreStatusCodes } = options;
Expand Down
2 changes: 2 additions & 0 deletions lib/util/http/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface HttpOptions {

token?: string;
memCache?: boolean;
repoCache?: boolean;
}

export interface EtagCache<T = any> {
Expand All @@ -81,6 +82,7 @@ export interface InternalHttpOptions extends HttpOptions {
responseType?: 'json' | 'buffer';
method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
parseJson?: ParseJsonFunction;
repoCache?: boolean;
}

export interface HttpHeaders extends IncomingHttpHeaders {
Expand Down

0 comments on commit 23a334c

Please sign in to comment.