We're undertaking a series of large data imports that are currently implemented using migrate.

The source table is located on the same database server as the core Drupal database - indeed it's in the same database. The table currently contains a total of 500,000 records (but will end up larger than that).

We're currently undertaking an initial import, so are running as:

drush mi Migration --limit="9 minutes"

as a periodic scheduled job.

Initial partial imports were perfectly performant. However - as the number of imported items has increased our migrate jobs (Being run through drush) are getting less, and less effective. We've identified that the bottleneck is migrate working out which rows it needs to import. Query logging led us to the following query being repeated over and over again with incrementing OFFSETs:

SELECT l.*
FROM 
import_table l
WHERE  (provider = 'xxxx') 
LIMIT 1000 OFFSET 90000

Given the size of our data set it's imperitive that we set a batch_size on the process - loading 500,000 records into memory isn't going to work :)

However - it seems that doing so is causing migrate to have to read all rows in chunks of 1,000 in order to establish if they need migrating instead of joining onto the map table and making the MySQL server do the work. Looking into sql.inc I found the following code:

if (isset($options['batch_size'])) {
      $this->batchSize = $options['batch_size'];
      // Joining to the map table is incompatible with batching, disable it.
      $options['map_joinable'] = FALSE;
    }

I'm not sure why having a batch_size would be incompatible with joining onto the map table especially as it seems that the two things are likely to go hand in hand with large data sets?

Is this something that can be resolved easily? We'd be happy to help investigate / test or contribute if someone thinks this is achievable. Equally if someone has investigated, and discounted this already that'd be great to know and we'll probably need to roll a custom solution.

CommentFileSizeAuthor
#2 migrate-scalability.diff935 bytesleewillis77

Comments

leewillis77 created an issue. See original summary.

leewillis77’s picture

Status: Active » Needs review
StatusFileSize
new935 bytes

I've dug a little further into this. It seems that the "incompatibility" was introduced by the patch for https://www.drupal.org/node/2415597.

Reading through the notes on that I can see where the issue lies, the relevant bit from the comments is.

"the premise of the LIMIT clause is that each time we execute the query, the base query (without the LIMIT) returns exactly the same set of rows in the same order."

With this in the mind the code currently adds a range in the format (batchNumber * batchSize, batchSize), e.g. you for a batch size of 1000, you get

->range(0,1000)
->range(1000,1000)
->range(2000,1000)
->range(3000,1000)

The original report goes on to note:

"However, if we're joining with the map table, which we're populating as we go, then each batch of 2000 rows the base query is 2000 rows shorter. So, on our second batch we're expecting to get rows 2001-4000, but since rows 1-2000 have been eliminated from the base query, the rows we want to fetch are now rows 1-2000 and we skip them."

The solution in that issue was to block the use of mapJoinable when batches are in use, but it seems like an alternative solution would be to consistently use ->range(0,batchSize) when mapJoinable is set?

The only thing I could see wrong with this would be if some items fail to be processed on a run. In this case they could repeatedly appear in subsequent batches. If you ended up with batchSize items "failing" then potential you'd get starvation where the same items are repeatedly attempted - is that possible here?

Conceptual patch attached - I'd welcome any feedback on whether this is a sensible approach or if there's something else I'm not considering? If someone thinks it's sensible and worth progressing, I'm happy to put in the time to test on our current project.

mikeryan’s picture

I think that's only workable during initial import - if you're dealing with updated content using track_changes, or doing a --update, you're going to be going over the same stuff repeatedly.