Как ускорить обновление отношений между таблицами после того, как одна или обе таблицы уже сохранены? - PullRequest
0 голосов
/ 27 апреля 2018

Вопрос: быстро обновляйте и сохраняйте отношения между таблицами с большим количеством данных после того, как обе или одна таблица уже сохранены.

У меня есть пять таблиц TvGenres, TvSubgenre, TvProgram, Channels, TvSchedules со связями между ними, как показано на изображении ниже

coredata Relationship

Теперь проблема в том, что загрузка всех данных происходит в последовательности, основанной на предыдущих данных, и в отличие от SQLite, мне нужно установить отношения между ними, и для этого мне нужно снова и снова искать таблицу и устанавливать связь между ними, которая является временной. так как я могу сделать это быстрее

Я использую 2 разных подхода для решения, но оба работают не так, как ожидалось

Сначала позвольте мне рассказать, как работает загрузка

Сначала я выбираю все детали каналов на основе языков пользователя. Из каналов я выбираю все расписания на следующую неделю (это много данных (около 30 000+)) И из данных расписаний я выбираю все данные программ (это опять-таки много данных)

Подход 1,

Загрузите все данные и создайте их список объектов, а затем сохраните их сразу после завершения загрузки, но установление отношений между ними требует времени и, что самое страшное, теперь цикл повторяется дважды, так как сначала я должен выполнить цикл для создания всего класса список, а затем цикл снова, чтобы сохранить их в табличном представлении и все еще не решить проблему, требующую много времени для отношений.

Подход 2

Загрузка по одному, например загрузка каналов, сохранение их, загрузка расписаний, сохранение их, загрузка программ и сохранение их в основных данных. Все в порядке, но теперь каналы связаны с расписанием, а расписания связаны с программами и устанавливают отношение, в то время как я храню расписания, я также выбираю канал, связанный с этим расписанием, и затем устанавливаю отношение, то же самое для программы и расписаний, и это занимает время, приведенное ниже, это код, так как я могу решить эту проблему или как я должен загрузить и сохранить, чтобы она стала как можно быстрее.

Код только для хранения расписаний

func saveScheduleDataToCoreData(withScheduleList scheduleList: [[String : Any]], completionBlock: @escaping (_ programIds: [String]?) -> Void) {
    let start = DispatchTime.now()
    let context = coreDataStack.managedObjectContext

    var progIds = [String]()
    context.performAndWait {
        var scheduleTable: TvSchedule!

        for (index,response) in scheduleList.enumerated() {
            let schedule: TvScheduleInformation = TvScheduleInformation(json: response )
            scheduleTable = TvSchedule(context: context)
            scheduleTable.channelId = schedule.channelId
            scheduleTable.programId = schedule.programId
            scheduleTable.startTime = schedule.startTime
            scheduleTable.endTime = schedule.endTime
            scheduleTable.day = schedule.day
            scheduleTable.languageId = schedule.languageId
            scheduleTable.isReminderSet = false

            //if I comment out the below code then it reduce the time significantly from 5 min to 34.74 s
            let tvChannelRequest: NSFetchRequest<Channels> = Channels.fetchRequest()
            tvChannelRequest.predicate = NSPredicate(format: "channelId == %d", schedule.channelId)
            tvChannelRequest.fetchLimit = 1
            do {
                let channelResult = try context.fetch(tvChannelRequest)
                if channelResult.count == 1 {
                    let channelTable = channelResult[0]
                    scheduleTable.channel = channelTable
                }
            }
            catch {
                print("Error: \(error)")
            }
            progIds.append(String(schedule.programId))
            //storeing after 1000 schedules 
            if index % 1000 == 0 {
                print(index)
                do {
                    try context.save()
                } catch let error as NSError {
                    print("Error saving schdeules object context! \(error)")
                }

            }
        }
    }
    let end = DispatchTime.now()
    let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
    print("Saving \(scheduleList.count) Schedules takes \(nanoTime) nano time")
    coreDataStack.saveContext()
    completionBlock(progIds)
}

Также, как сделать правильное пакетное сохранение с помощью пула autoreleas

PS: Все материалы, которые я нашел, относящиеся к базовым данным, стоят дорого и стоят более 3 тыс., А с бесплатной информацией не так много, просто базовые вещи, даже в документах Apple нет большого кода, связанного с настройкой производительности и пакетными обновлениями. и передача отношений. Заранее благодарим за помощь.

1 Ответ

0 голосов
/ 23 августа 2018

У меня были такие проекты раньше. Не существует единственного решения, которое решает все проблемы, но вот некоторые вещи, которые очень помогают:

Очереди и дозировка

Кажется, вы попытались вставить все сразу, а затем попытались сделать это один за другим. В моих приложениях я нашел около 300, чтобы быть лучшим размером партии, но вы должны настроить его, чтобы увидеть, что работает в вашем приложении, это может быть целых 5000 или всего лишь 100. Начните с 300 и настройте, чтобы увидеть, что лучше Результаты.

У вас происходит несколько процессов, вы упомянули загрузку и сохранение в базе данных, но я не удивлюсь, если вы упомянули еще кое-что. Очереди (NSOperationsQueue) являются удивительным инструментом для этого. Вы можете подумать, что создание очереди замедлит работу, но это не так. Когда вы пытаетесь сделать слишком много одновременно, дела идут медленно.

Таким образом, у вас есть одна очередь для загрузки информации (я рекомендую ограничить до 4 одновременных запросов) и одна, которая сохраняет данные в базовые данные (ограничьте одновременность до 1, чтобы не было конфликтов записи). По завершении каждой задачи загрузки она разбивает результаты на более управляемые размеры и очереди для записи в базу данных. Не беспокойтесь, если последняя партия немного меньше остальных.

Каждая вставка в основные данные создает свой собственный контекст, выполняет собственные выборки, сохраняет их и затем отбрасывает объекты. Не обращайтесь к этим объектам откуда-либо еще, и вы получите сбои - данные ядра не являются потокобезопасными Также пишите только в основные данные, используя эту очередь, иначе вы получите конфликты записи. (см. Параллелизм NSPersistentContainer для сохранения в основные данные для получения дополнительной информации об этой установке).

Поиск карт

Теперь вы пытаетесь вставить 300-ю сущности, и каждая из них должна найти или создать связанные сущности. У вас может быть несколько функций, которые разбросаны вокруг, чтобы выполнить это. Если вы запрограммируете это без учета производительности, вы легко сможете выполнить 300 или даже 600 запросов на выборку. Вместо этого вы делаете один выбор fetchRequest.predicate = NSPredicate(format: "channelId IN %@", objectIdsIamDealingWithNow). После извлечения преобразуйте массив в словарь с идентификатором в качестве ключа

  var lookup:[String: TvSchedule] = [:]
  if let results = try? context.fetch(fetchRequest) {
      results.forEach { if let channelId = $0.channelId { lookup[channelId] = $0  } }
  }

Если у вас есть эта справочная карта, не теряйте ее. Передайте это каждой функции, которая нуждается в этом. Если вы создаете объекты, подумайте о том, чтобы потом вставить их в словарь. Внутри основной операции с данными этот поисковый словарь - ваш лучший друг. Будьте осторожны, хотя. Этот объект содержит управляемые объекты, которые не являются потокобезопасными. Вы создаете этот объект в начале вашего блока базы данных и в конце должны отказаться от него.

Предпочитать фильтрацию отношений по выборкам

У вас нет какого-либо кода, который явно имеет с этим дело, но я не удивлюсь, если вы столкнетесь с ним. Допустим, у вас есть определенный TvSchedule, и вы хотите найти все программы, которые есть в расписании, на определенном языке. Естественный способ сделать это - создать предикат, который будет выглядеть примерно так: «TvSchedule ==% @ AND langId ==% @». Но на самом деле это гораздо быстрее сделать mySchedule.programs.filter {%@.langId = myLangId }

Анализ и настройка

Я вижу, вы уже добавляете логи в код, чтобы увидеть, как много времени занимает, это действительно хорошо. Я также рекомендовал бы использовать инструменты профиля xCode. Это может быть очень полезно для поиска функций, которые занимают большую часть времени.

...