-
Notifications
You must be signed in to change notification settings - Fork 683
feat(spanner): make affinity keys configurable via spanner_grpc_confi… #8157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2070d68
e820c2b
aade2f1
691788b
c83dad4
6bc433c
2bcddda
10729a1
ff1f481
31d6079
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -56,6 +56,22 @@ export type Rows = Array<Row | Json>; | |||||||||||||
| const RETRY_INFO_TYPE = 'type.googleapis.com/google.rpc.retryinfo'; | ||||||||||||||
| const RETRY_INFO_BIN = 'google.rpc.retryinfo-bin'; | ||||||||||||||
|
|
||||||||||||||
| let nextAffinityId = 0; | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Injects a key-value pair into the gaxOpts.otherArgs.options object | ||||||||||||||
| * without mutating the original. | ||||||||||||||
| */ | ||||||||||||||
| function injectGaxOpt(existingOpts: any, key: string, value: any): any { | ||||||||||||||
| return Object.assign({}, existingOpts, { | ||||||||||||||
| otherArgs: Object.assign({}, existingOpts?.otherArgs, { | ||||||||||||||
| options: Object.assign({}, existingOpts?.otherArgs?.options, { | ||||||||||||||
| [key]: value, | ||||||||||||||
| }), | ||||||||||||||
| }), | ||||||||||||||
| }); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| export interface TimestampBounds { | ||||||||||||||
| strong?: boolean; | ||||||||||||||
| minReadTimestamp?: PreciseDate | spannerClient.protobuf.ITimestamp; | ||||||||||||||
|
|
@@ -292,6 +308,9 @@ export class Snapshot extends EventEmitter { | |||||||||||||
| | undefined | ||||||||||||||
| | null; | ||||||||||||||
| id?: Uint8Array | string; | ||||||||||||||
| public _affinityKey?: string; | ||||||||||||||
| public _bindGaxOpts?: CallOptions; | ||||||||||||||
| public _unbindGaxOpts?: CallOptions; | ||||||||||||||
| multiplexedSessionPreviousTransactionId?: Uint8Array | string; | ||||||||||||||
| ended: boolean; | ||||||||||||||
| metadata?: spannerClient.spanner.v1.ITransaction; | ||||||||||||||
|
|
@@ -361,8 +380,60 @@ export class Snapshot extends EventEmitter { | |||||||||||||
| this.ended = false; | ||||||||||||||
| this.session = session; | ||||||||||||||
| this.queryOptions = Object.assign({}, queryOptions); | ||||||||||||||
| this.request = session.request.bind(session); | ||||||||||||||
| this.requestStream = session.requestStream.bind(session); | ||||||||||||||
| // If the session is multiplexed, generate a unique affinity key for this | ||||||||||||||
| // specific transaction/snapshot. This allows requests using the same shared | ||||||||||||||
| // multiplexed session to be distributed across different gRPC channels. | ||||||||||||||
| if (session.metadata && session.metadata.multiplexed) { | ||||||||||||||
| this._affinityKey = `mux-affinity-${process.pid}-${nextAffinityId++}`; | ||||||||||||||
| // Pre-construct and cache the bind gax options to avoid creating | ||||||||||||||
| // a new object on every request, which improves performance. | ||||||||||||||
| this._bindGaxOpts = { | ||||||||||||||
| otherArgs: { | ||||||||||||||
| options: { | ||||||||||||||
| affinityKey: this._affinityKey, | ||||||||||||||
| }, | ||||||||||||||
| }, | ||||||||||||||
| }; | ||||||||||||||
| // Pre-construct and cache the unbind gax options. This explicitly signals | ||||||||||||||
| // the channel factory to release the affinity mapping when the transaction ends. | ||||||||||||||
| this._unbindGaxOpts = { | ||||||||||||||
| otherArgs: { | ||||||||||||||
| options: { | ||||||||||||||
| affinityKey: this._affinityKey, | ||||||||||||||
| unbind: true, | ||||||||||||||
| }, | ||||||||||||||
| }, | ||||||||||||||
| }; | ||||||||||||||
| } | ||||||||||||||
| this.request = (config: any, callback: Function) => { | ||||||||||||||
| if (this._affinityKey) { | ||||||||||||||
| if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) { | ||||||||||||||
| config.gaxOpts = this._bindGaxOpts as any; | ||||||||||||||
| } else { | ||||||||||||||
| config.gaxOpts = injectGaxOpt( | ||||||||||||||
| config.gaxOpts, | ||||||||||||||
| 'affinityKey', | ||||||||||||||
| this._affinityKey, | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| return session.request(config, callback); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| this.requestStream = (config: any) => { | ||||||||||||||
| if (this._affinityKey) { | ||||||||||||||
| if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) { | ||||||||||||||
| config.gaxOpts = this._bindGaxOpts as any; | ||||||||||||||
| } else { | ||||||||||||||
| config.gaxOpts = injectGaxOpt( | ||||||||||||||
| config.gaxOpts, | ||||||||||||||
| 'affinityKey', | ||||||||||||||
| this._affinityKey, | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| return session.requestStream(config); | ||||||||||||||
| }; | ||||||||||||||
|
alkatrivedi marked this conversation as resolved.
Comment on lines
+408
to
+436
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mutating the this.request = (config: any, callback: Function) => {
if (this._affinityKey) {
let gaxOpts;
if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) {
gaxOpts = this._bindGaxOpts as any;
} else {
gaxOpts = injectGaxOpt(
config.gaxOpts,
'affinityKey',
this._affinityKey,
);
}
config = Object.assign({}, config, {gaxOpts});
}
return session.request(config, callback);
};
this.requestStream = (config: any) => {
if (this._affinityKey) {
let gaxOpts;
if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) {
gaxOpts = this._bindGaxOpts as any;
} else {
gaxOpts = injectGaxOpt(
config.gaxOpts,
'affinityKey',
this._affinityKey,
);
}
config = Object.assign({}, config, {gaxOpts});
}
return session.requestStream(config);
}; |
||||||||||||||
|
|
||||||||||||||
| const readOnly = Snapshot.encodeTimestampBounds(options || {}); | ||||||||||||||
| this._options = {readOnly}; | ||||||||||||||
|
|
@@ -1024,6 +1095,20 @@ export class Snapshot extends EventEmitter { | |||||||||||||
|
|
||||||||||||||
| this.ended = true; | ||||||||||||||
| process.nextTick(() => this.emit('end')); | ||||||||||||||
|
|
||||||||||||||
| if (this._affinityKey) { | ||||||||||||||
| const database = this.session.parent as Database; | ||||||||||||||
| const spanner = database.parent.parent as Spanner; | ||||||||||||||
| const client = spanner.clients_.get('SpannerClient') as any; | ||||||||||||||
|
Comment on lines
+1100
to
+1102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accessing nested properties like
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| if (client?.spannerStub) { | ||||||||||||||
| Promise.resolve(client.spannerStub) | ||||||||||||||
| .then((stub: any) => { | ||||||||||||||
| stub?.getChannel?.()?.unbind?.(this._affinityKey); | ||||||||||||||
| }) | ||||||||||||||
| .catch(() => {}); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
|
|
@@ -2374,7 +2459,7 @@ export class Transaction extends Dml { | |||||||||||||
| typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; | ||||||||||||||
| const callback = | ||||||||||||||
| typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!; | ||||||||||||||
| const gaxOpts = | ||||||||||||||
| let gaxOpts = | ||||||||||||||
| 'gaxOptions' in options ? (options as CommitOptions).gaxOptions : options; | ||||||||||||||
|
|
||||||||||||||
| const mutations = this._queuedMutations; | ||||||||||||||
|
|
@@ -2449,12 +2534,20 @@ export class Transaction extends Dml { | |||||||||||||
| span.addEvent('Starting Commit'); | ||||||||||||||
|
|
||||||||||||||
| const database = this.session.parent as Database; | ||||||||||||||
| if (this._affinityKey) { | ||||||||||||||
| if (!gaxOpts || Object.keys(gaxOpts).length === 0) { | ||||||||||||||
| gaxOpts = this._unbindGaxOpts as any; | ||||||||||||||
| } else { | ||||||||||||||
| gaxOpts = injectGaxOpt(gaxOpts, 'unbind', true); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| this.request( | ||||||||||||||
| { | ||||||||||||||
| client: 'SpannerClient', | ||||||||||||||
| method: 'commit', | ||||||||||||||
| reqOpts, | ||||||||||||||
| gaxOpts: gaxOpts, | ||||||||||||||
| gaxOpts, | ||||||||||||||
| headers: injectRequestIDIntoHeaders( | ||||||||||||||
| headers, | ||||||||||||||
| this.session, | ||||||||||||||
|
|
@@ -2787,7 +2880,7 @@ export class Transaction extends Dml { | |||||||||||||
| | spannerClient.spanner.v1.Spanner.RollbackCallback, | ||||||||||||||
| cb?: spannerClient.spanner.v1.Spanner.RollbackCallback, | ||||||||||||||
| ): void | Promise<void> { | ||||||||||||||
| const gaxOpts = | ||||||||||||||
| let gaxOpts = | ||||||||||||||
| typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {}; | ||||||||||||||
| const callback = | ||||||||||||||
| typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!; | ||||||||||||||
|
|
@@ -2812,6 +2905,14 @@ export class Transaction extends Dml { | |||||||||||||
| addLeaderAwareRoutingHeader(headers); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (this._affinityKey) { | ||||||||||||||
| if (!gaxOpts || Object.keys(gaxOpts).length === 0) { | ||||||||||||||
| gaxOpts = this._unbindGaxOpts as any; | ||||||||||||||
| } else { | ||||||||||||||
| gaxOpts = injectGaxOpt(gaxOpts, 'unbind', true); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| this.request( | ||||||||||||||
| { | ||||||||||||||
| client: 'SpannerClient', | ||||||||||||||
|
|
||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.