我需要读取和写入大量记录(大约1000个)。以下示例需要长达20分钟才能写入1000条记录,并且只需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
感谢您的帮助。
一些可能有用的额外信息:
经过更多测试后,我注意到编写前100条记录只需5秒钟。每条记录的写入时间比前一条记录要长。在第500条记录中,写入每条记录需要5秒钟。
如果我中断程序(当它开始感觉很慢)并再次启动它(不清除数据库),它会以我打断它时所写的相同(慢)速度写入记录。
这是否让我们更接近解决方案?
答案 0 :(得分:3)
Nic,这可能不是你所希望的答案,但现在是:
我建议用这种性能实验来改变框架;例如,根本不使用客户端。我将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()
中间计时是每100次插入的打印机,您很快就会看到插入时间与插入项目的数量成二次方,而非线性。这是因为列表更新/user[0]/notes <- [key|noteids]
显然导致整个列表再次被写入。 AFAIK我们有优化以避免这种情况,但要么是我错了,要么是因为某些原因他们不在这里工作 - 我会试着调查一下,一旦我知道更多就会让你知道。
除了前面提到的优化之外,在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秒钟。不幸的是,这个功能非常具有实验性,因此没有文档记录,正如你所看到的,它确实在这个例子中爆炸。
我担心我们并没有真正计划改进(3)和(4),因为我们意识到提供符合工业标准的内部数据库解决方案并不是很现实。因此,目前我们正集中精力将Opa与现有的No-SQL数据库紧密集成。我们希望在未来几周内有一些好消息。
我会尝试从我们的团队中了解有关此问题的更多信息,如果我得知错过了/出错了,我会做出更正。