我需要读取和写入大量记录(大约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)
Run Code Online (Sandbox Code Playgroud)
我也试过通过交易获得笔记(如下所示).看起来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
Run Code Online (Sandbox Code Playgroud)
谢谢你的帮助.
一些可能有用的额外信息:
经过更多测试后,我注意到编写前100条记录只需5秒钟.每条记录的写入时间比前一条记录要长.在第500条记录中,每条记录需要5秒钟.
如果我中断程序(当它开始感觉很慢)并再次启动它(不清除数据库),它会以我打断它时写入的相同(慢)速度写入记录.
这会让我们更接近解决方案吗?
尼克,这可能不是您所希望的答案,但它是:
我建议这种性能实验改变框架;例如根本不使用客户端。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()
Run Code Online (Sandbox Code Playgroud)由于中间计时是每 100 个插入的打印机,您很快就会发现插入时间与插入项目的数量成二次方,而不是线性的。这是因为列表更新/user[0]/notes <- [key|noteids]显然导致整个列表被再次写入。据我所知,我们进行了优化来避免这种情况,但要么我错了,要么由于某些原因它们在这里不起作用——我会尝试调查这一点,并在我了解更多信息后通知您。
除了前面提到的优化之外,在 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()
Run Code Online (Sandbox Code Playgroud)
您可以在其中设置填充数据库大约需要 2 秒。不幸的是,这个功能还处于实验阶段,因此没有文档记录,正如您将看到的,它确实在这个示例中爆炸了。
恐怕我们并不真正打算改进(3)和(4),因为我们意识到提供符合工业标准的内部数据库解决方案不太现实。因此,目前我们正在集中精力将 Opa 与现有 No-SQL 数据库紧密集成。我们希望在未来几周内能听到一些好消息。
我将尝试从我们的团队中了解有关此问题的更多信息,如果我发现我遗漏/出错了,我将进行纠正。