Skip to content

Commit fd32547

Browse files
committed
Limit size of batches sent to server.
1 parent 3c23d1f commit fd32547

File tree

6 files changed

+85
-16
lines changed

6 files changed

+85
-16
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ You can configure Amplitude by passing an object as the third argument to the `i
9494
|------------|----------------------------------------------------------------------------------|-----------|
9595
| saveEvents | If `true`, saves events to localStorage and removes them upon successful upload.<br><i>NOTE:</i> Without saving events, events may be lost if the user navigates to another page before events are uploaded. | `true` |
9696
| savedMaxCount | Maximum number of events to save in localStorage. If more events are logged while offline, old events are removed. | 1000 |
97+
| uploadBatchSize | Maximum number of events to send to the server per request. | 100 |
9798
| includeUtm | If `true`, finds utm parameters in the query string or the __utmz cookie, parses, and includes them as user propeties on all events uploaded. | `false` |
9899

99100

amplitude.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ var DEFAULT_OPTIONS = {
139139
saveEvents: true,
140140
sessionTimeout: 30 * 60 * 1000,
141141
unsentKey: 'amplitude_unsent',
142+
uploadBatchSize: 100,
142143
};
143144
var LocalStorageKeys = {
144145
LAST_EVENT_ID: 'amplitude_lastEventId',
@@ -186,6 +187,7 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) {
186187
this.options.platform = opt_config.platform || this.options.platform;
187188
this.options.language = opt_config.language || this.options.language;
188189
this.options.sessionTimeout = opt_config.sessionTimeout || this.options.sessionTimeout;
190+
this.options.uploadBatchSize = opt_config.uploadBatchSize || this.options.uploadBatchSize;
189191
this.options.savedMaxCount = opt_config.savedMaxCount || this.options.savedMaxCount;
190192
}
191193

@@ -503,9 +505,10 @@ Amplitude.prototype.sendEvents = function() {
503505
this.options.apiEndpoint + '/';
504506

505507
// Determine how many events to send and track the maximum event id sent in this batch.
506-
var maxEventId = this._unsentEvents[this._unsentEvents.length - 1].eventId;
508+
var numEvents = Math.min(this._unsentEvents.length, this.options.uploadBatchSize);
509+
var maxEventId = this._unsentEvents[numEvents - 1].event_id;
507510

508-
var events = JSON.stringify(this._unsentEvents.slice(0, this._unsentEvents.length));
511+
var events = JSON.stringify(this._unsentEvents.slice(0, numEvents));
509512
var uploadTime = new Date().getTime();
510513
var data = {
511514
client: this.options.apiKey,
@@ -516,10 +519,10 @@ Amplitude.prototype.sendEvents = function() {
516519
};
517520

518521
var scope = this;
519-
new Request(url, data).send(function(response) {
522+
new Request(url, data).send(function(status, response) {
520523
scope._sending = false;
521524
try {
522-
if (response === 'success') {
525+
if (status === 200 && response === 'success') {
523526
//log('sucessful upload');
524527
scope.removeEvents(maxEventId);
525528

@@ -532,6 +535,12 @@ Amplitude.prototype.sendEvents = function() {
532535
if (scope._unsentEvents.length > 0) {
533536
scope.sendEvents();
534537
}
538+
} else if (status === 413) {
539+
//log('request too large');
540+
// The server complained about the length of the request.
541+
// Backoff and try again.
542+
scope.options.uploadBatchSize = Math.floor(numEvents / 2);
543+
scope.sendEvents();
535544
}
536545
} catch (e) {
537546
//log('failed upload');
@@ -1498,9 +1507,7 @@ Request.prototype.send = function(callback) {
14981507
xhr.open('POST', this.url, true);
14991508
xhr.onreadystatechange = function() {
15001509
if (xhr.readyState === 4) {
1501-
if (xhr.status === 200) {
1502-
callback(xhr.responseText);
1503-
}
1510+
callback(xhr.status, xhr.responseText);
15041511
}
15051512
};
15061513
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

amplitude.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/amplitude.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var DEFAULT_OPTIONS = {
2727
saveEvents: true,
2828
sessionTimeout: 30 * 60 * 1000,
2929
unsentKey: 'amplitude_unsent',
30+
uploadBatchSize: 100,
3031
};
3132
var LocalStorageKeys = {
3233
LAST_EVENT_ID: 'amplitude_lastEventId',
@@ -74,6 +75,7 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) {
7475
this.options.platform = opt_config.platform || this.options.platform;
7576
this.options.language = opt_config.language || this.options.language;
7677
this.options.sessionTimeout = opt_config.sessionTimeout || this.options.sessionTimeout;
78+
this.options.uploadBatchSize = opt_config.uploadBatchSize || this.options.uploadBatchSize;
7779
this.options.savedMaxCount = opt_config.savedMaxCount || this.options.savedMaxCount;
7880
}
7981

@@ -391,9 +393,10 @@ Amplitude.prototype.sendEvents = function() {
391393
this.options.apiEndpoint + '/';
392394

393395
// Determine how many events to send and track the maximum event id sent in this batch.
394-
var maxEventId = this._unsentEvents[this._unsentEvents.length - 1].eventId;
396+
var numEvents = Math.min(this._unsentEvents.length, this.options.uploadBatchSize);
397+
var maxEventId = this._unsentEvents[numEvents - 1].event_id;
395398

396-
var events = JSON.stringify(this._unsentEvents.slice(0, this._unsentEvents.length));
399+
var events = JSON.stringify(this._unsentEvents.slice(0, numEvents));
397400
var uploadTime = new Date().getTime();
398401
var data = {
399402
client: this.options.apiKey,
@@ -404,10 +407,10 @@ Amplitude.prototype.sendEvents = function() {
404407
};
405408

406409
var scope = this;
407-
new Request(url, data).send(function(response) {
410+
new Request(url, data).send(function(status, response) {
408411
scope._sending = false;
409412
try {
410-
if (response === 'success') {
413+
if (status === 200 && response === 'success') {
411414
//log('sucessful upload');
412415
scope.removeEvents(maxEventId);
413416

@@ -420,6 +423,12 @@ Amplitude.prototype.sendEvents = function() {
420423
if (scope._unsentEvents.length > 0) {
421424
scope.sendEvents();
422425
}
426+
} else if (status === 413) {
427+
//log('request too large');
428+
// The server complained about the length of the request.
429+
// Backoff and try again.
430+
scope.options.uploadBatchSize = Math.floor(numEvents / 2);
431+
scope.sendEvents();
423432
}
424433
} catch (e) {
425434
//log('failed upload');

src/xhr.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ Request.prototype.send = function(callback) {
2222
xhr.open('POST', this.url, true);
2323
xhr.onreadystatechange = function() {
2424
if (xhr.readyState === 4) {
25-
if (xhr.status === 200) {
26-
callback(xhr.responseText);
27-
}
25+
callback(xhr.status, xhr.responseText);
2826
}
2927
};
3028
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

test/amplitude.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,60 @@ describe('Amplitude', function() {
259259
amplitude2.init(apiKey);
260260
assert.deepEqual(amplitude2._unsentEvents, []);
261261
});
262+
263+
it('should batch events sent', function() {
264+
amplitude.init(apiKey, null, {uploadBatchSize: 10});
265+
266+
amplitude._sending = true;
267+
for (var i = 0; i < 15; i++) {
268+
amplitude.logEvent('Event', {index: i});
269+
}
270+
amplitude._sending = false;
271+
272+
amplitude.logEvent('Event', {index: 100});
273+
274+
assert.lengthOf(server.requests, 1);
275+
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
276+
assert.lengthOf(events, 10);
277+
assert.deepEqual(events[0].event_properties, {index: 0});
278+
assert.deepEqual(events[9].event_properties, {index: 9});
279+
280+
server.respondWith('success');
281+
server.respond();
282+
283+
assert.lengthOf(server.requests, 2);
284+
var events = JSON.parse(querystring.parse(server.requests[1].requestBody).e);
285+
assert.lengthOf(events, 6);
286+
assert.deepEqual(events[0].event_properties, {index: 10});
287+
assert.deepEqual(events[5].event_properties, {index: 100});
288+
});
289+
290+
it('should back off on 413 status', function() {
291+
amplitude.init(apiKey, null, {uploadBatchSize: 10});
292+
293+
amplitude._sending = true;
294+
for (var i = 0; i < 15; i++) {
295+
amplitude.logEvent('Event', {index: i});
296+
}
297+
amplitude._sending = false;
298+
299+
amplitude.logEvent('Event', {index: 100});
300+
301+
assert.lengthOf(server.requests, 1);
302+
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
303+
assert.lengthOf(events, 10);
304+
assert.deepEqual(events[0].event_properties, {index: 0});
305+
assert.deepEqual(events[9].event_properties, {index: 9});
306+
307+
server.respondWith([413, {}, ""]);
308+
server.respond();
309+
310+
assert.lengthOf(server.requests, 2);
311+
var events = JSON.parse(querystring.parse(server.requests[1].requestBody).e);
312+
assert.lengthOf(events, 5);
313+
assert.deepEqual(events[0].event_properties, {index: 0});
314+
assert.deepEqual(events[4].event_properties, {index: 4});
315+
});
262316
});
263317

264318
describe('optOut', function() {

0 commit comments

Comments
 (0)