Skip to content

Conversation

@sgarg-CS
Copy link
Contributor

@sgarg-CS sgarg-CS commented Nov 10, 2025

PLUGIN-1936

Issue : OutOfMemory (OOM) Errors seen in the ServiceNow Connector frequently when transfering large number of records

RCA: The entire response is converted into string and the JSON is parsed in memory leading to OOM issues.

Proposed Fix: Read the records incrementally i.e. one by one in a streaming manner.

Changes :

  • Added a new method: prepareResponseWithBodyAndStream :in the RESTAPIResponse class to store a reusable InputStream in the RESTAPIResponse object. Also changed the logic for preparing the responseBody in the String format
  • Added openNextPage to initialise the Gson's JsonReader and read the result array in the response and closeCurrentPage to close the JsonReader
  • nextKeyValue() : Refactored the existing logic to stream one record at a time.
  • Changed the return type of the following methods from Map<String, String> to RESTAPIResponse
    • fetchData()
    • fetchTableRecordsRetryableMode()
    • fetchTableRecords()

@sgarg-CS sgarg-CS marked this pull request as ready for review November 25, 2025 04:20
}

/**
* Check whether the result is empty or not.

Choose a reason for hiding this comment

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

Also explain how are we checking is result is empty

return GSON.fromJson(ja, type);
}

public List<Map<String, String>> parseResponseToResultListOfMap(InputStream in) throws ServiceNowAPIException {

Choose a reason for hiding this comment

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

Why are we creating a List of map? Instead we can directly read it as a JsonObject, something like:

gson.fromJson(reader, ServiceNowRecordObject.class);

String startDate, String endDate, int offset,
int limit) throws ServiceNowAPIException {
final List<Map<String, String>> results = new ArrayList<>();
//final List<Map<String, String>> results = new ArrayList<>();

Choose a reason for hiding this comment

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

please remove

final RestAPIResponse[] restAPIResponse = new RestAPIResponse[1];
Callable<Boolean> fetchRecords = () -> {
results.addAll(fetchTableRecords(tableName, valueType, startDate, endDate, offset, limit));
// results.addAll(fetchTableRecords(tableName, valueType, startDate, endDate, offset, limit));

Choose a reason for hiding this comment

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

please remove

Callable<Boolean> fetchRecords = () -> {
results.addAll(fetchTableRecords(tableName, valueType, startDate, endDate, offset, limit));
// results.addAll(fetchTableRecords(tableName, valueType, startDate, endDate, offset, limit));
restAPIResponse[0] = fetchTableRecords(tableName, valueType, startDate, endDate, offset, limit);

Choose a reason for hiding this comment

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

can you please explain what are we trying to do here? why restAPIResponse is an array

private final ServiceNowMultiSourceConfig multiSourcePluginConf;
private ServiceNowTableAPIClientImpl restApi;
private final Gson gson = new Gson();
private final Type mapType = new TypeToken<Map<String, String>>() { }.getType();

Choose a reason for hiding this comment

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

should use an object instead of map

return false;
}
this.jsonReader = new JsonReader(new InputStreamReader(in, StandardCharsets.UTF_8));
this.jsonReader.setLenient(true);

Choose a reason for hiding this comment

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

why we need this?

JsonToken top;
try {
top = jsonReader.peek();
LOG.info("Peeking JSON token for table {}: {}", tableName, top);

Choose a reason for hiding this comment

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

please remove, it should be a debug log

}

public void closeCurrentPage() {
LOG.info("Closing current page for table {}", tableName);

Choose a reason for hiding this comment

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

not needed?

return restAPIResponse;
}

private boolean openNextPage() throws IOException, ServiceNowAPIException {

Choose a reason for hiding this comment

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

looks redundant, as same function is present in src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReader.java

can we abstract these out in a common file. Same comment for nextKeyValue()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants