Опа: как эффективно читать / писать большое количество записей - PullRequest
5 голосов
/ 22 октября 2011

Проблема

Мне нужно прочитать и написать большое количество записей (около 1000).В приведенном ниже примере для записи 1000 записей требуется 20 минут, а для их чтения - 12 секунд (когда я выполняю свои тесты «чтения», я закомментирую строку do create_notes()).

Источник

Это полный пример (который собирается и запускается).Он выводит только вывод на консоль (не в браузер).

type User.t =
  { id : int
  ; notes : list(int) // a list of note ids
  }

type Note.t =
  { id : int
  ; uid : int // id of the user this note belongs to
  ; content : string
  }

db /user : intmap(User.t)
db /note : intmap(Note.t)

get_notes(uid:int) : list(Note.t) =
  noteids = /user[uid]/notes
  List.fold(
    (h,acc -> 
      match ?/note[h] with
      | {none} -> acc
      | {some = note} -> [note|acc]
    ), noteids, [])

create_user() =
  match ?/user[0] with
  | {none} -> /user[0] <- {id=0 notes=[]}
  | _ -> void

create_note() =
  key = Db.fresh_key(@/note)
  do /note[key] <- {id = key uid = 0 content = "note"}
  noteids = /user[0]/notes
  /user[0]/notes <- [key|noteids]

create_notes() =
  repeat(1000, create_note)

page() =
  do create_user()
  do create_notes()
  do Debug.alert("{get_notes(0)}")
  <>Notes</>

server = one_page_server("Notes", page)

Еще одна вещь

Я также пытался получать заметки с помощью транзакции (показано ниже).Похоже, Db.transaction может быть правильным инструментом, но я не нашел способа успешно использовать его.Я обнаружил, что этот метод get_notes_via_transaction работает так же медленно, как get_notes.

get_notes_via_transaction(uid:int) : list(Note.t) =
  result = Db.transaction( ->
    noteids = /user[uid]/notes
    List.fold(
      (h,acc -> 
        match ?/note[h] with
        | {none} -> acc
        | {some = note} -> [note|acc]
      ), noteids, [])
  )
  match result with
  | {none} -> []
  |~{some} -> some

Спасибо за вашу помощь.

Редактировать: Подробнее

Aнемного дополнительной информации, которая может оказаться полезной:

После дополнительного тестирования я заметил, что написание первых 100 записей занимает всего 5 секунд.Каждая запись занимает больше времени, чем предыдущая.На 500-й записи для записи каждой записи требуется 5 секунд.

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

Это приближает нас к решению?

1 Ответ

3 голосов
/ 26 октября 2011

Ник, это, вероятно, не тот ответ, на который ты надеялся, но вот он:

  1. Я бы предложил для такого рода экспериментов с производительностью изменить структуру; например, не использовать клиента вообще. Я бы заменил код функции create_node на этот:

    counter = Reference.create(0)
    create_note() =
      key = Db.fresh_key(@/note)
      do /note[key] <- {id = key uid = 0 content = "note"}
      noteids = /user[0]/notes
      do Reference.update(counter, _ + 1)
      do /user[0]/notes <- [key|noteids]
      cntr = Reference.get(counter)
      do if mod(cntr, 100) == 0 then
           Log.info("notes", "{cntr} notes created")
         else
           void
      void
    
    import stdlib.profiler
    
    create_notes() =
      repeat(1000, -> P.execute(create_note, "create_note"))
    
    P = Server_profiler
    
    _ =
      do P.init()
      do create_user()
      do create_notes()
      do P.execute(-> get_notes(0), "get_notes(0)")
      P.summarize()
    
  2. Если для каждых 100 вставок установлено промежуточное время печати, вы быстро увидите, что время вставки является квадратичным по отношению к количеству вставленных элементов, а не линейным. Это происходит из-за обновления списка /user[0]/notes <- [key|noteids], которое, по-видимому, приводит к повторной записи всего списка . AFAIK У нас были оптимизации, чтобы избежать этого, но либо я ошибаюсь, либо по некоторым причинам они здесь не работают - я постараюсь разобраться в этом и сообщу вам, как только я узнаю больше.

  3. Помимо ранее упомянутой оптимизации, лучший подход к моделированию этих данных в Opa будет использовать наборы, как в следующей программе:

    type Note.t =
    { id : int
    ; uid : int // id of the user this note belongs to
    ; content : string
    }
    
    db /user_notes[{user_id; note_id}] : { user_id : int; note_id : int }
    db /note : intmap(Note.t)
    
    get_notes(uid:int) : list(Note.t) =
      add_note(acc : list(Note.t), user_note) =
        note = /note[user_note.note_id]
        [note | acc]
      noteids = /user_notes[{user_id=uid}] : dbset({user_id:int; note_id:int})
      DbSet.fold(noteids, [], add_note)
    
    counter = Reference.create(0)
    
    create_note() =
      key = Db.fresh_key(@/note)
      do /note[key] <- {id = key uid = 0 content = "note"}
      do DbVirtual.write(@/user_notes[{user_id=0}], {note_id = key})
      do Reference.update(counter, _ + 1)
      cntr = Reference.get(counter)
      do if mod(cntr, 100) == 0 then
           Log.info("notes", "{cntr} notes created")
         else
           void
      void
    
    import stdlib.profiler
    
    create_notes() =
      repeat(1000, -> Server_profiler.execute(create_note, "create_note"))
    
    _ =
      do Server_profiler.init()
      do create_notes()
      do Server_profiler.execute(-> get_notes(0), "get_notes(0)")
      Server_profiler.summarize()
    

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

  4. Боюсь, что мы не планируем улучшать (3) и (4), поскольку поняли, что предоставление собственного решения БД, соответствующего промышленным стандартам, не очень реалистично. Поэтому в настоящее время мы концентрируем все наши усилия на тесной интеграции Opa с существующими базами данных No-SQL. Мы надеемся получить хорошие новости об этом в ближайшие недели.

Я постараюсь узнать больше об этой проблеме от нашей команды и исправлю, если узнаю, что я что-то упустил / ошибся.

...