Skip to content
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

feat: add starting/max rates to BulkWriterOptions #1305

Merged
merged 11 commits into from
Sep 25, 2020
Merged

Conversation

thebrianchen
Copy link

Following up from the 555 discussion by adding option to set the initial and maximum rate of the throttler in BulkWriter.

@thebrianchen thebrianchen requested review from a team as code owners September 22, 2020 20:42
@thebrianchen thebrianchen self-assigned this Sep 22, 2020
@google-cla google-cla bot added the cla: yes This human has signed the Contributor License Agreement. label Sep 22, 2020
@codecov
Copy link

codecov bot commented Sep 22, 2020

Codecov Report

Merging #1305 into master will increase coverage by 0.04%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1305      +/-   ##
==========================================
+ Coverage   98.46%   98.51%   +0.04%     
==========================================
  Files          30       30              
  Lines       18648    18754     +106     
  Branches     1435     1450      +15     
==========================================
+ Hits        18362    18475     +113     
+ Misses        283      276       -7     
  Partials        3        3              
Impacted Files Coverage Δ
dev/src/bulk-writer.ts 99.75% <100.00%> (+0.85%) ⬆️
dev/src/index.ts 97.57% <100.00%> (+0.06%) ⬆️
dev/src/rate-limiter.ts 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 67b0aba...5fa80f8. Read the comment docs.

@product-auto-label product-auto-label bot added the api: firestore Issues related to the googleapis/nodejs-firestore API. label Sep 23, 2020
dev/src/bulk-writer.ts Outdated Show resolved Hide resolved
dev/src/bulk-writer.ts Outdated Show resolved Hide resolved
}
}

if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use validateInteger with a NumericRangeOptions to make sure that the provided numbers are >= 0.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better, done.

Comment on lines 761 to 768
if (typeof options.disableThrottling !== 'boolean') {
throw new Error(
`${invalidArgumentMessage(
arg,
'bulkWriter() options argument'
)} "disableThrottling" is not a boolean.`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to validate invalid TypeScript usage. You should only validate API input that is valid to the transpiler, but not to the SDK (such as disableThrottling and initialOpsPerSecond set together)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

describe('options', () => {
it('requires object', async () => {
const firestore = await createInstance();
expect(() => firestore.bulkWriter(42 as InvalidApiUsage)).to.throw(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to keep this in mind for next time! I'll probably end up cutting out the validation code at some point, which means we won't have any InvalidApiUsage instances that would otherwise require the expect-error comment.

readonly disableThrottling?: boolean;

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian Sep 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the APIs I have been dealing with lately do the following:

export interface BulkWriterOptions {
readonly throttling?: boolean | {initialOpsPerSecond?: number; maxOpsPerSecond?: number; };
}

This would match terser and eslint, but we might want to gather more feedback (maybe from @Feiyang1).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attempted

@schmidt-sebastian schmidt-sebastian removed their assignment Sep 23, 2020
Brian Chen and others added 5 commits September 23, 2020 14:16
Co-authored-by: Sebastian Schmidt <mrschmidt@google.com>
Co-authored-by: Sebastian Schmidt <mrschmidt@google.com>
Copy link
Author

@thebrianchen thebrianchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used the union types for BulkWriterOptions. I think my constructor logic could be cleaner -- please advise :)

I'll assign to Fei after you think it's in a good place.

}
}

if (
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better, done.

describe('options', () => {
it('requires object', async () => {
const firestore = await createInstance();
expect(() => firestore.bulkWriter(42 as InvalidApiUsage)).to.throw(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to keep this in mind for next time! I'll probably end up cutting out the validation code at some point, which means we won't have any InvalidApiUsage instances that would otherwise require the expect-error comment.

Comment on lines 372 to 376
const startingRate =
typeof options?.throttling !== 'boolean' &&
options?.throttling?.initialOpsPerSecond !== undefined
? options.throttling.initialOpsPerSecond
: STARTING_MAXIMUM_OPS_PER_SECOND;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is:

Suggested change
const startingRate =
typeof options?.throttling !== 'boolean' &&
options?.throttling?.initialOpsPerSecond !== undefined
? options.throttling.initialOpsPerSecond
: STARTING_MAXIMUM_OPS_PER_SECOND;
const startingRate = options?.throttling?.initialOpsPerSecond ??
STARTING_MAXIMUM_OPS_PER_SECOND;

I tested it with this code:

const STARTING_MAXIMUM_OPS_PER_SECOND = 1337;
const options1 : any = {throttling:false};
const options2 : any = {throttling:true};
const options3 : any = {throttling:{initialOpsPerSecond:10}};

function printThrottler(options?:any) {
    console.log(options?.throttling?.initialOpsPerSecond ??STARTING_MAXIMUM_OPS_PER_SECOND)
}

printThrottler(options1);
printThrottler(options2);
printThrottler(options3);

This prints 1337, 1337, 10.

Same below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a union type, initialOpsPerSecond is not a number unless I explicity check it's not a boolean first.

With the additional change to lower the initial rate to match the max rate if it's below the default, I refactored it to use if statements, which is hopefully more readable.

* Validates the use of 'value' as BulkWriterOptions.
*
* @private
* @param arg The argument name or argument index (for varargs methods).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably be more specific since this is only used for BulkWriterOptions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed used of arg as a parameter.

* @param value The object to validate.
* @throws if the input is not a valid BulkWriterOptions object.
*/
function validateBulkWriterOptions(arg: string | number, value: unknown): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be a string.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

'initialOpsPerSecond',
options.throttling.initialOpsPerSecond,
{
minValue: 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be 1, doesn't it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


if ('maxOpsPerSecond' in options.throttling) {
validateInteger('maxOpsPerSecond', options.throttling.maxOpsPerSecond, {
minValue: 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 also doesn't quite work here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

);
}

if ('maxOpsPerSecond' in options.throttling) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you change this to options.throttling.maxOpsPerSecond !== undefined, you can get rid of the bang below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

});

if (
'initialOpsPerSecond' in options.throttling &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same: options.throttling. initialOpsPerSecond !== undefined

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

!('initialOpsPerSecond' in options.throttling) &&
STARTING_MAXIMUM_OPS_PER_SECOND > options.throttling.maxOpsPerSecond!
) {
throw new Error(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty unexpected, especially since the default value is not known. We should instead adjust the starting value.

BTW, I think we should somehow squeeze DEFAULT into the name of STARTING_MAXIMUM_OPS_PER_SECOND, since the constant is no longer required to be the maximum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

*/
readonly throttling?:
| boolean
| {initialOpsPerSecond?: number; maxOpsPerSecond?: number};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. Did you check with @Feiyang1 if he thinks this is a useable API?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine to me. Autocomplete will help people figure it out. Currently we are only describing the config params in the comment. I would make it clear in the comment that it can be either a boolean or a config object (boolean to enable/disable, config to enable and set config values).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was going to ask him after, but he jumped in :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also want to update the comment per Fei's suggestion?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I had already added some more to the comments, but this time around I also explicitly stated that setting it to false disables throttling.

@schmidt-sebastian schmidt-sebastian removed their assignment Sep 24, 2020
@@ -351,22 +356,46 @@ export class BulkWriter {

constructor(
private readonly firestore: Firestore,
enableThrottling: boolean
private readonly options?: firestore.BulkWriterOptions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private readonly options?: firestore.BulkWriterOptions
options?: firestore.BulkWriterOptions

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

*/
readonly throttling?:
| boolean
| {initialOpsPerSecond?: number; maxOpsPerSecond?: number};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also want to update the comment per Fei's suggestion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: firestore Issues related to the googleapis/nodejs-firestore API. cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants