Forum Discussion

KenvM's avatar
KenvM
Frequent Visitor
9 years ago
Solved

Getting Data from Elastic Stack with REST with Scroll

Hello,

 

I'm pulling data into Power BI using the Elastic Stack REST API, but I'm running into a problem when trying to pull more than 10k records.

 

In order to do so, I need to use the scroll function: https://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-scroll.html

 

The first call:

 

 

GET /_search?scroll=1m 
{ "query": ... }

Will return the first set of results plus a scroll id: 

 

DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4...

 

Which is used for subsequent calls:

 

 

GET /_search {
  "scroll_id": "DXF1Z..."
}

 

 

Each call returns a set of records plus a new scroll id. The scroll id is then passed onto the next record, and so on. 

 

For performance reasons, I don't want to increase the number of records that can be returned by the REST API.

 

Is it possible to solve this using Power BI?


Thanks,


Ken

 

  • KenvM's avatar
    KenvM
    9 years ago

    Thanks for pointing me in the right direction.

     

    For anyone else that runs into this problem, here's the first iteration of a working function: 

     

    let
        RecursiveElasticFetch = (queryUrl, scrollUrl, scrollId, counter) =>
    
        let
            Counter = if (counter = null) then 0 else counter,
    
            Results = if (scrollId = null) then            
                Json.Document(Web.Contents(queryUrl))
            else
                Json.Document(Web.Contents(scrollUrl&scrollId, [Headers=[MyHeader=Text.From(Counter)]])),
    
            ParsedResults = Table.FromList(Results[hits][hits], Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    
            Return = if (Counter < 10) then
                ParsedResults & RecursiveElasticFetch(queryUrl, scrollUrl, Results[_scroll_id], Counter+1)
            else
                ParsedResults 
        in
            Return 
    in
        RecursiveElasticFetch

    For the parameters:

     

    queryUrl - The query URL that kicks off the search: http://elasticsrch-dev:9200/intranet*/_search?scroll=1m&source={....}

    scrollUrl - The URL used for subsequent searches (the scroll id is appended at the end): http://elasticsrch-dev:9200/_search/scroll?scroll=1m&scroll_id=

    scrollId - Used to pass the scroll_id to subsequent calls. Leave it blank for the first call

    counter - Used to limit the number of iterations. Leave it blank for the first call. 

     

39 Replies

Replies have been turned off for this discussion
    • KenvM's avatar
      KenvM
      Frequent Visitor

      Thanks for pointing me in the right direction.

       

      For anyone else that runs into this problem, here's the first iteration of a working function: 

       

      let
          RecursiveElasticFetch = (queryUrl, scrollUrl, scrollId, counter) =>
      
          let
              Counter = if (counter = null) then 0 else counter,
      
              Results = if (scrollId = null) then            
                  Json.Document(Web.Contents(queryUrl))
              else
                  Json.Document(Web.Contents(scrollUrl&scrollId, [Headers=[MyHeader=Text.From(Counter)]])),
      
              ParsedResults = Table.FromList(Results[hits][hits], Splitter.SplitByNothing(), null, null, ExtraValues.Error),
      
              Return = if (Counter < 10) then
                  ParsedResults & RecursiveElasticFetch(queryUrl, scrollUrl, Results[_scroll_id], Counter+1)
              else
                  ParsedResults 
          in
              Return 
      in
          RecursiveElasticFetch

      For the parameters:

       

      queryUrl - The query URL that kicks off the search: http://elasticsrch-dev:9200/intranet*/_search?scroll=1m&source={....}

      scrollUrl - The URL used for subsequent searches (the scroll id is appended at the end): http://elasticsrch-dev:9200/_search/scroll?scroll=1m&scroll_id=

      scrollId - Used to pass the scroll_id to subsequent calls. Leave it blank for the first call

      counter - Used to limit the number of iterations. Leave it blank for the first call. 

       

      • wendt_1's avatar
        wendt_1
        Regular Visitor

        Hey KenvM,

         

        Thanks for sharing this iteration! Have you made any new changes? I'm having an issue after the second loop of this script. First call returns one set of records, second call returns a new set, third and all subsequent calls returns a repeat set of data (not sure if from first or second call, but I could verify if needed). 

        Have you encountered this yourself, and if so, have you found a solution?

        Link to a more detailed description on the Elastic forums here: Elastic Forums

        Thanks!

  • Anonymous's avatar
    Anonymous
    Not applicable

    Hi Dear all, Im currently working with this function but I need to connect at ElasticSearch server using credentials (Basic username:password) , Do you know if this is possible?

     

    I could not get a good result :(

     

     

    thanks a lot for your help.

  • Also, I added API Key functionality.

     

    Query (you will need to modify this accordingly):

     

    let
        url = "https://CHANGEME:9200/weblogs/_search?scroll=1m",
        scrollURL = "https://CHANGEME:9200/_search/scroll",
        batchSize = "10000",
        urlBody = Text.Combine({"{""size"":",batchSize,",""query"":{""match_all"":{}}}"}),
        scrollBody = "{""scroll"":""1m"",""scroll_id"":""",
        scrollID = null,
        counter = null,
        scrollEND = """}",
        apiKey = "CHANGEME",
        Source = RecursiveElasticFetch(url, scrollURL, batchSize, urlBody, scrollBody, scrollID, counter, scrollEND, apiKey),
        #"Renamed Columns" = Table.RenameColumns(Source,{{"Column1", "es"}}),
        #"Expanded es" = Table.ExpandRecordColumn(#"Renamed Columns", "es", {"_index", "_type", "_id", "_score", "_source"}, {"es._index", "es._type", "es._id", "es._score", "es._source"})
    in
        #"Expanded es"

     

     

      

    Function:

    (Right click on the query name on the left hand side and then click create function)

     

    let
        RecursiveElasticFetch = (url, scrollURL, batchSize, urlBody, scrollBody, scrollID, counter, scrollEND, apiKey) =>
    
        let
            Results = if (scrollID = null) then
                //Initial Query
                Json.Document(Web.Contents(url, [Headers=[#"Authorization"=Text.Combine({"ApiKey ",apiKey,""}), #"Content-Type"="application/json"], Content = Text.ToBinary(urlBody)]))
            else
                //All other queries execute this to gather results from the scroll api even if the scroll id changes.
                Json.Document(Web.Contents(scrollURL, [Headers=[#"Authorization"=Text.Combine({"ApiKey ",apiKey,""}), #"Content-Type"="application/json", MyHeader=Text.From(counter)], Content = Text.ToBinary(scrollBody&scrollID&scrollEND)])),
    
            //If this is the first time the function runs, the counter should be null so this will dynamically calcuate how many times this function needs to run.
            counter = if (counter = null) then
                //Dynamically get the counter - Note: You can uncomment the next line for testing and then comment out the Number.RoundUp
                7
                //Number.RoundUp(Results[hits][total][value]/Number.FromText(batchSize))
            else
                counter,
    
            //Store the hits from the ElasticSearch query into ParsedResults and if results already exist, append more results to generate the full table of events.
            ParsedResults = Table.FromList(Results[hits][hits], Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    
            Return = if (counter > 0) then
                ParsedResults & RecursiveElasticFetch(url, scrollURL, batchSize, urlBody, scrollBody, Results[_scroll_id], counter-1, scrollEND, apiKey)
            else
                ParsedResults
        in
            Return
    in
        RecursiveElasticFetch

     

     

  • Anonymous's avatar
    Anonymous
    Not applicable

    Hey guys!
    Does anyone here have an idea how to do this with an Elasticsearch composite query?

    The scroll method worked, however due to the large number of records, it is cumbersome to pull the data.

    in composite queries there is no scroll_id parameter, there is only after_key, which is key and value