-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memory improvements in FastJsonNode #5088
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 1 of 3 files at r1, 2 of 2 files at r2.
Reviewable status: all files reviewed, 17 unresolved discussions (waiting on @ashish-goswami)
query/arena.go, line 33 at r2 (raw file):
x.AssertTrue(size > 0) return &arena{ n: 1,
Don't need this.
query/arena.go, line 34 at r2 (raw file):
return &arena{ n: 1, buf: make([]byte, 0, size),
Append a byte to the buf, to have the offset start from 1.
query/arena.go, line 38 at r2 (raw file):
} func (a *arena) put(b []byte) int {
Deal with concurrency issues. Return uint32
query/arena.go, line 51 at r2 (raw file):
} func (a *arena) get(offset int) []byte {
Deal with concurrency. Take uint32
query/outputnode.go, line 90 at r2 (raw file):
) func (e *encoder) idForAttr(attr string) uint16 {
Thread safe.
query/outputnode.go, line 102 at r2 (raw file):
} func (e *encoder) attrForID(id uint16) string {
Thread safe.
query/outputnode.go, line 108 at r2 (raw file):
// Panic for now. panic("id not found in map")
Return empty string. Don't panic.
query/outputnode.go, line 118 at r2 (raw file):
// Bytes 3-4 contains attr. // Bit MSB and (MSB-1) contains isChild and list fields values. meta uint64
Move this up.
query/outputnode.go, line 133 at r2 (raw file):
func (fj *fastJsonNode) setIsChild(isChild bool) { if isChild { fj.meta |= (uint64(1) << 63)
Define consts.
query/outputnode.go, line 139 at r2 (raw file):
func (fj *fastJsonNode) setList(list bool) { if list { fj.meta |= (uint64(1) << 62)
Define consts.
query/outputnode.go, line 144 at r2 (raw file):
func (fj *fastJsonNode) getAttr() uint16 { return uint16((fj.meta & (uint64(0xFFFF) << 32)) >> 32)
Define consts.
query/outputnode.go, line 162 at r2 (raw file):
func (fj *fastJsonNode) getList() bool { if (fj.meta & (uint64(1) << 62)) > 0 {
return this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, use variable encoding instead of fixed 4 bytes for length.
Reviewable status: all files reviewed, 17 unresolved discussions (waiting on @ashish-goswami)
* Move most of information in Encoder * Comment outputnode_test.go for now
query/outputnode.go
Outdated
// enc = newEncoder() | ||
) | ||
|
||
func (e *encoder) idForAttr(attr string) uint16 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
receiver name e should be consistent with previous receiver name enc for encoder (from golint
)
query/outputnode.go
Outdated
return e.seqNo | ||
} | ||
|
||
func (e *encoder) attrForID(id uint16) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
receiver name e should be consistent with previous receiver name enc for encoder (from golint
)
query/outputnode.go
Outdated
fj.attrs = append(fj.attrs, makeScalarNode(attr, false, []byte(fmt.Sprintf("\"%#x\"", uid)), | ||
false)) | ||
|
||
fj.appendAttrs(enc, []fastJsonNode{enc.makeScalarNode(attr, false, []byte(fmt.Sprintf("\"%#x\"", uid)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line is 104 characters (from lll
)
query/outputnode.go
Outdated
if cmp == 0 { | ||
return n[i].order < n[j].order | ||
} | ||
cmp := strings.Compare(n.enc.attrForID(n.nodes[i].getAttr(n.enc)), n.enc.attrForID(n.nodes[j].getAttr(n.enc))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line is 111 characters (from lll
)
query/outputnode.go
Outdated
seqNo uint16 | ||
arena *arena | ||
|
||
root fastJsonNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
field root
is unused (from unused
)
query/outputnode.go
Outdated
} | ||
|
||
// For debugging. | ||
func (fj fastJsonNode) printMeta(enc *encoder) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func fastJsonNode.printMeta
is unused (from unused
)
* Convert method of fastJsonNode to that of encoder
query/outputnode.go
Outdated
type fastJsonNode int | ||
|
||
// newFastJsonNode returns a fastJsonNode with its meta set to 0. | ||
func (enc *encoder) newFastJsonNode() fastJsonNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
receiver name enc should be consistent with previous receiver name e for encoder (from golint
)
query/outputnode.go
Outdated
func (fj *fastJsonNode) AddListValue(attr string, v types.Val, list bool) { | ||
// newFastJsonNodeWithAttr returns a fastJsonNode with its attr set to attr, | ||
// with all other meta set to their default value. | ||
func (enc *encoder) newFastJsonNodeWithAttr(attr uint16) fastJsonNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
receiver name enc should be consistent with previous receiver name e for encoder (from golint
)
query/outputnode.go
Outdated
fj.attrs = append(fj.attrs, makeScalarNode(attr, false, []byte(fmt.Sprintf("\"%#x\"", uid)), | ||
false)) | ||
|
||
enc.appendAttrs(fj, []fastJsonNode{enc.makeScalarNode(attr, false, []byte(fmt.Sprintf("\"%#x\"", uid)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line is 104 characters (from lll
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking very good. Let's run this again and see what we can optimize.
Reviewed 1 of 3 files at r3, 2 of 2 files at r5.
Reviewable status: all files reviewed, 29 unresolved discussions (waiting on @ashish-goswami)
query/outputnode.go, line 720 at r5 (raw file):
return } g := enc.newFastJsonNodeWithAttr(enc.idForAttr(fname))
Too long. Can be: enc.newNodeWithAttr(fname)
?
The benchmarks are not correct. The first case, is just creating fastJsonNode and not storing it anywhere. The second case is storing them in a slice. So, the first case is under counting the memory usage. Therefore, there's a bigger win here. In any case, how do the results look with the query and the data? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dismissed @golangcibot from 14 discussions.
Reviewable status: 0 of 3 files reviewed, 15 unresolved discussions (waiting on @ashish-goswami, @golangcibot, and @manishrjain)
query/arena.go, line 71 at r1 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
File is not
gofmt
-ed with-s
(fromgofmt
)
Done.
query/arena.go, line 33 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Don't need this.
Done.
query/arena.go, line 34 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Append a byte to the buf, to have the offset start from 1.
Done.
query/arena.go, line 38 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Deal with concurrency issues. Return uint32
Returning uint32 now.
query/arena.go, line 51 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Deal with concurrency. Take uint32
Taking uint32 now.
query/outputnode.go, line 154 at r1 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
S1008: should use 'return ' instead of 'if { return }; return ' (from
gosimple
)
Done.
query/outputnode.go, line 162 at r1 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
S1008: should use 'return ' instead of 'if { return }; return ' (from
gosimple
)
Done.
query/outputnode.go, line 118 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Move this up.
Done.
query/outputnode.go, line 133 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Define consts.
Done.
query/outputnode.go, line 139 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Define consts.
Done.
query/outputnode.go, line 144 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Define consts.
Done.
query/outputnode.go, line 162 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
return this.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status: 0 of 3 files reviewed, 15 unresolved discussions (waiting on @ashish-goswami, @golangcibot, and @manishrjain)
query/outputnode.go, line 720 at r5 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Too long. Can be:
enc.newNodeWithAttr(fname)
?
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's starting to look good. Let's address the comments and then move it to a formal PR and get it in.
Reviewed 3 of 3 files at r6.
Reviewable status: all files reviewed, 23 unresolved discussions (waiting on @ashish-goswami, @golangcibot, and @manishrjain)
query/arena.go, line 50 at r6 (raw file):
// Note: for now this function can only put buffers such that: // varint(len(b)) + len(b) < math.MaxUint32. func (a *arena) put(b []byte) (uint8, uint32) {
uint64, but just say that the MSB 24 bits are not used.
Also, return error. That'd be preferred. Otherwise, you can return a 0, and then ensure that it gets handled at the place which has error handling.
query/arena.go, line 60 at r6 (raw file):
if int64(offset+w+len(b)) > int64(math.MaxUint32) { buf := make([]byte, 0, minSize)
These bufs can be retrieved from a sync.Pool.
query/arena.go, line 65 at r6 (raw file):
last = len(a.bufs) - 1 offset = 1 x.AssertTruef(last < int(math.MaxUint8),
Do not assert. Let's return error instead.
query/arena.go, line 67 at r6 (raw file):
x.AssertTruef(last < int(math.MaxUint8), "Number of bufs in arena should be < math.MaxUint8") x.AssertTruef(int64(offset+w+len(b)) < int64(math.MaxUint32),
Asserting won't help, a user can then take down Dgraph server. Return error.
query/arena.go, line 77 at r6 (raw file):
} func (a *arena) get(idx uint8, offset uint32) []byte {
uint64. Also, let's return error.
query/arena.go, line 84 at r6 (raw file):
// First read length, then read actual buffer. size, r := binary.Varint(a.bufs[idx][offset:])
I'd check whether the bufs idx is valid and also if the offset is valid. And return error.
query/outputnode.go, line 133 at r6 (raw file):
const ( msbBit = 0x8000000000000000
Add comments for each and every one of them.
query/outputnode.go, line 136 at r6 (raw file):
secondMsbBit = 0x4000000000000000 setBytes76 = 0x00FFFF0000000000 unsetBytes76 = 0xFF0000FFFFFFFFFF
unsetBytes76 = ^setBytes76
// however Go does bit flipping.
query/arena.go
Outdated
) | ||
|
||
const ( | ||
minSize = 1 * 1024 // 1 KB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minSize
is unused (from varcheck
)
* Also address some of review comments
query/outputnode.go
Outdated
if sg.Params.GetUid { | ||
uc.SetUID(childUID, "uid") | ||
enc.SetUID(uc, childUID, enc.idForAttr("uid")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error return value of enc.SetUID
is not checked (from errcheck
)
query/outputnode.go
Outdated
default: | ||
if pc.Params.Alias == "" && len(pc.Params.Langs) > 0 && pc.Params.Langs[0] != "*" { | ||
fieldName += "@" | ||
fieldName += strings.Join(pc.Params.Langs, ":") | ||
} | ||
|
||
if pc.Attr == "uid" { | ||
dst.SetUID(uid, pc.fieldName()) | ||
enc.SetUID(dst, uid, enc.idForAttr(pc.fieldName())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error return value of enc.SetUID
is not checked (from errcheck
)
query/outputnode.go
Outdated
if (sg.Params.GetUid && !dst.IsEmpty()) || sg.Params.Shortest { | ||
dst.SetUID(uid, "uid") | ||
if (sg.Params.GetUid && !enc.IsEmpty(dst)) || sg.Params.Shortest { | ||
enc.SetUID(dst, uid, enc.idForAttr("uid")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error return value of enc.SetUID
is not checked (from errcheck
)
query/arena.go
Outdated
errArenaFull = errors.New("arena is full") | ||
errInvalidOffset = errors.New("arena get performed with invalid offset") | ||
|
||
maxArenaSize = math.MaxUint32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maxArenaSize
is unused (from varcheck
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status: 0 of 4 files reviewed, 28 unresolved discussions (waiting on @ashish-goswami, @golangcibot, and @manishrjain)
query/arena.go, line 60 at r6 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
These bufs can be retrieved from a sync.Pool.
Done.
query/arena.go, line 65 at r6 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Do not assert. Let's return error instead.
Removed this for now, only using single buffer.
query/arena.go, line 77 at r6 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
uint64. Also, let's return error.
Done.
query/arena.go, line 84 at r6 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
I'd check whether the bufs idx is valid and also if the offset is valid. And return error.
Done.
query/arena.go, line 28 at r7 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
minSize
is unused (fromvarcheck
)
Removed it.
query/arena.go, line 32 at r8 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
maxArenaSize
is unused (fromvarcheck
)
Getting used now.
query/outputnode.go, line 108 at r2 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Return empty string. Don't panic.
Done.
query/outputnode.go, line 133 at r6 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
Add comments for each and every one of them.
Done.
query/outputnode.go, line 1191 at r8 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
Error return value of
enc.SetUID
is not checked (fromerrcheck
)
Done.
query/outputnode.go, line 1261 at r8 (raw file):
Previously, golangcibot (Bot from GolangCI) wrote…
Error return value of
enc.SetUID
is not checked (fromerrcheck
)
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 2 of 4 files at r8, 2 of 2 files at r9.
Reviewable status: all files reviewed, 31 unresolved discussions (waiting on @ashish-goswami, @golangcibot, and @manishrjain)
query/arena.go, line 50 at r9 (raw file):
// Start offset from 1, to avoid reading bytes when offset is storing default value(0) in // fastJsonNode. Hence append dummy byte. buf := make([]byte, 0, size)
This needs to be in a pool.
query/arena.go, line 72 at r9 (raw file):
} // First put length of buffer(varint encoded), then put actual buffer. sizeBuf := (a.sizeBufPool.Get()).([]byte)
No, don't use pool for this.
query/outputnode.go, line 1230 at r9 (raw file):
} encodeAsList := pc.List && len(lang) == 0 err := enc.AddListValue(dst, enc.idForAttr(fieldNameWithTag), sv, encodeAsList)
if err := ...; err != nil
Would you mind writing some unit tests for this. I've started with a few simple tests in fastjson_test.go |
I have added some tests for fastJsonNode. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 3 of 3 files at r10.
Reviewable status: all files reviewed, 24 unresolved discussions (waiting on @ashish-goswami, @golangcibot, @manishrjain, and @pawanrawal)
query/arena.go, line 72 at r9 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
No, don't use pool for this.
var sizeBuf [binary.MaxVarintLen64]byte
query/arena.go, line 76 at r10 (raw file):
w := binary.PutVarint(sizeBuf, int64(len(b))) offset := len(a.buf) if int64(len(a.buf)+w+len(b)) > int64(maxArenaSize) {
maxArenaSize can just be int64.
query/arena.go, line 77 at r10 (raw file):
offset := len(a.buf) if int64(len(a.buf)+w+len(b)) > int64(maxArenaSize) { return 0, errArenaFull
Do mention the size of the arena.
query/outputnode.go, line 136 at r6 (raw file):
Previously, manishrjain (Manish R Jain) wrote…
unsetBytes76 = ^setBytes76
// however Go does bit flipping.
unsetBytes76 = uint64(^setBytes76)
query/outputnode.go, line 113 at r10 (raw file):
// TODO(Ashish): check for overflow. enc.seqNo++
just return error if possible.
query/outputnode.go, line 148 at r10 (raw file):
msbBit = 0x8000000000000000 // Value with all bits set to 1 for bytes 7 and 6. setBytes76 = 0x00FFFF0000000000
setBytes76 uint64 = ...
query/outputnode.go, line 786 at r10 (raw file):
} func handleCountUIDNodes(sg *SubGraph, enc *encoder, n fastJsonNode, count int) (bool, error) {
Either make it part of subgraph, or encoder.
Fixes #5124, DGRAPH-1170 While converting a subgraph to JSON response, an intermediate data structure called fastJsonNode tree is formed. We have observed when response to be returned is big(specially in recurse queries), this datastructure itself can occupy lot of memory and leading to OOM in some cases. This PR aims to reduce space occupied by fastJsonNode tree. fastJsonNode tree is kind of n-ary tree, where each fastJsonNode maintains some meta data and list of its children. This PR tries to reduce space occupied by each node in following way: For each response a separate datastructure called encoder is formed which is responsible for maintaining meta data for all fastJsonNodes. encoder has metaSlice and childrenMap where all meta and children list are maintained for all fastJsonNodes. Index at which meta for a fastJsonNode is present, becomes its value and hence type of a fastJsonNode is uint32. meta for a fastJsonNode(present at int(fastJsonNode) value in metaSlice) is of uint64 type. It stores all the info for a fastJsonNode. Most significant bit stores value of List field, bytes 7-6 stores attr id and bytes 4 to 1 stores arena offset((explained below)). encoder has attrMap which has mapping of predicates to unique uint16 number. encoder also has arena. arena is a larger []byte, which stores bytes for each leaf node. It offsets are stored in fastJsonNode meta. arena stores same []byte only once and keeps a map for memhash([]byte) to offset mapping. On this change, I am able to run some of queries which were resulting in OOM current master. Profile for a query when RSS usage was around 30GB master profile on the query: File: dgraph Build ID: 4009644c7dfb41957358d88f228b977b2fb552c7 Type: inuse_space Time: Apr 11, 2020 at 10:31pm (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 26.74GB, 98.87% of 27.05GB total Dropped 133 nodes (cum <= 0.14GB) Showing top 10 nodes out of 42 flat flat% sum% cum cum% 16.62GB 61.43% 61.43% 16.62GB 61.43% github.com/dgraph-io/dgraph/query.makeScalarNode 4.23GB 15.63% 77.06% 4.23GB 15.63% github.com/dgraph-io/dgraph/query.(*fastJsonNode).New 2.03GB 7.51% 84.57% 2.03GB 7.51% github.com/dgraph-io/dgraph/query.stringJsonMarshal 1.64GB 6.08% 90.65% 15.99GB 59.10% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListValue 0.88GB 3.25% 93.90% 5.24GB 19.36% github.com/dgraph-io/dgraph/query.(*fastJsonNode).SetUID 0.44GB 1.61% 95.52% 0.44GB 1.61% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListChild 0.36GB 1.32% 96.84% 0.39GB 1.45% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 0.25GB 0.92% 97.76% 0.25GB 0.92% github.com/dgraph-io/ristretto.newCmRow 0.16GB 0.6% 98.36% 0.16GB 0.6% github.com/dgraph-io/badger/v2/skl.newArena 0.14GB 0.51% 98.87% 0.14GB 0.51% github.com/dgraph-io/ristretto/z.(*Bloom).Size This PR profile: File: dgraph Build ID: 8fd737a95d4edf3ffb305638081766fd0044e99d Type: inuse_space Time: Apr 15, 2020 at 11:20am (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 15557.02MB, 98.53% of 15789.61MB total Dropped 168 nodes (cum <= 78.95MB) Showing top 10 nodes out of 60 flat flat% sum% cum cum% 6341.11MB 40.16% 40.16% 6341.11MB 40.16% github.com/dgraph-io/dgraph/query.(*encoder).appendAttrs 4598.84MB 29.13% 69.29% 4598.84MB 29.13% bytes.makeSlice 3591.16MB 22.74% 92.03% 3591.16MB 22.74% github.com/dgraph-io/dgraph/query.(*encoder).newNode 365.52MB 2.31% 94.34% 408.54MB 2.59% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 256MB 1.62% 95.97% 256MB 1.62% github.com/dgraph-io/ristretto.newCmRow 166.41MB 1.05% 97.02% 166.41MB 1.05% github.com/dgraph-io/badger/v2/skl.newArena 140.25MB 0.89% 97.91% 140.25MB 0.89% github.com/dgraph-io/ristretto/z.(*Bloom).Size 91.12MB 0.58% 98.48% 98.05MB 0.62% github.com/dgraph-io/dgraph/posting.(*List).Uids 6MB 0.038% 98.52% 122.23MB 0.77% github.com/dgraph-io/dgraph/worker.(*queryState).handleUidPostings.func1 0.64MB 0.004% 98.53% 387.17MB 2.45% github.com/dgraph-io/ristretto.NewCache
Fixes #5124, DGRAPH-1170 While converting a subgraph to JSON response, an intermediate data structure called fastJsonNode tree is formed. We have observed when response to be returned is big(specially in recurse queries), this datastructure itself can occupy lot of memory and leading to OOM in some cases. This PR aims to reduce space occupied by fastJsonNode tree. fastJsonNode tree is kind of n-ary tree, where each fastJsonNode maintains some meta data and list of its children. This PR tries to reduce space occupied by each node in following way: For each response a separate datastructure called encoder is formed which is responsible for maintaining meta data for all fastJsonNodes. encoder has metaSlice and childrenMap where all meta and children list are maintained for all fastJsonNodes. Index at which meta for a fastJsonNode is present, becomes its value and hence type of a fastJsonNode is uint32. meta for a fastJsonNode(present at int(fastJsonNode) value in metaSlice) is of uint64 type. It stores all the info for a fastJsonNode. Most significant bit stores value of List field, bytes 7-6 stores attr id and bytes 4 to 1 stores arena offset((explained below)). encoder has attrMap which has mapping of predicates to unique uint16 number. encoder also has arena. arena is a larger []byte, which stores bytes for each leaf node. It offsets are stored in fastJsonNode meta. arena stores same []byte only once and keeps a map for memhash([]byte) to offset mapping. On this change, I am able to run some of queries which were resulting in OOM current master. Profile for a query when RSS usage was around 30GB master profile on the query: File: dgraph Build ID: 4009644c7dfb41957358d88f228b977b2fb552c7 Type: inuse_space Time: Apr 11, 2020 at 10:31pm (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 26.74GB, 98.87% of 27.05GB total Dropped 133 nodes (cum <= 0.14GB) Showing top 10 nodes out of 42 flat flat% sum% cum cum% 16.62GB 61.43% 61.43% 16.62GB 61.43% github.com/dgraph-io/dgraph/query.makeScalarNode 4.23GB 15.63% 77.06% 4.23GB 15.63% github.com/dgraph-io/dgraph/query.(*fastJsonNode).New 2.03GB 7.51% 84.57% 2.03GB 7.51% github.com/dgraph-io/dgraph/query.stringJsonMarshal 1.64GB 6.08% 90.65% 15.99GB 59.10% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListValue 0.88GB 3.25% 93.90% 5.24GB 19.36% github.com/dgraph-io/dgraph/query.(*fastJsonNode).SetUID 0.44GB 1.61% 95.52% 0.44GB 1.61% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListChild 0.36GB 1.32% 96.84% 0.39GB 1.45% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 0.25GB 0.92% 97.76% 0.25GB 0.92% github.com/dgraph-io/ristretto.newCmRow 0.16GB 0.6% 98.36% 0.16GB 0.6% github.com/dgraph-io/badger/v2/skl.newArena 0.14GB 0.51% 98.87% 0.14GB 0.51% github.com/dgraph-io/ristretto/z.(*Bloom).Size This PR profile: File: dgraph Build ID: 8fd737a95d4edf3ffb305638081766fd0044e99d Type: inuse_space Time: Apr 15, 2020 at 11:20am (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 15557.02MB, 98.53% of 15789.61MB total Dropped 168 nodes (cum <= 78.95MB) Showing top 10 nodes out of 60 flat flat% sum% cum cum% 6341.11MB 40.16% 40.16% 6341.11MB 40.16% github.com/dgraph-io/dgraph/query.(*encoder).appendAttrs 4598.84MB 29.13% 69.29% 4598.84MB 29.13% bytes.makeSlice 3591.16MB 22.74% 92.03% 3591.16MB 22.74% github.com/dgraph-io/dgraph/query.(*encoder).newNode 365.52MB 2.31% 94.34% 408.54MB 2.59% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 256MB 1.62% 95.97% 256MB 1.62% github.com/dgraph-io/ristretto.newCmRow 166.41MB 1.05% 97.02% 166.41MB 1.05% github.com/dgraph-io/badger/v2/skl.newArena 140.25MB 0.89% 97.91% 140.25MB 0.89% github.com/dgraph-io/ristretto/z.(*Bloom).Size 91.12MB 0.58% 98.48% 98.05MB 0.62% github.com/dgraph-io/dgraph/posting.(*List).Uids 6MB 0.038% 98.52% 122.23MB 0.77% github.com/dgraph-io/dgraph/worker.(*queryState).handleUidPostings.func1 0.64MB 0.004% 98.53% 387.17MB 2.45% github.com/dgraph-io/ristretto.NewCache
Fixes #5124, DGRAPH-1170 While converting a subgraph to JSON response, an intermediate data structure called fastJsonNode tree is formed. We have observed when response to be returned is big(specially in recurse queries), this datastructure itself can occupy lot of memory and leading to OOM in some cases. This PR aims to reduce space occupied by fastJsonNode tree. fastJsonNode tree is kind of n-ary tree, where each fastJsonNode maintains some meta data and list of its children. This PR tries to reduce space occupied by each node in following way: For each response a separate datastructure called encoder is formed which is responsible for maintaining meta data for all fastJsonNodes. encoder has metaSlice and childrenMap where all meta and children list are maintained for all fastJsonNodes. Index at which meta for a fastJsonNode is present, becomes its value and hence type of a fastJsonNode is uint32. meta for a fastJsonNode(present at int(fastJsonNode) value in metaSlice) is of uint64 type. It stores all the info for a fastJsonNode. Most significant bit stores value of List field, bytes 7-6 stores attr id and bytes 4 to 1 stores arena offset((explained below)). encoder has attrMap which has mapping of predicates to unique uint16 number. encoder also has arena. arena is a larger []byte, which stores bytes for each leaf node. It offsets are stored in fastJsonNode meta. arena stores same []byte only once and keeps a map for memhash([]byte) to offset mapping. On this change, I am able to run some of queries which were resulting in OOM current master. Profile for a query when RSS usage was around 30GB master profile on the query: File: dgraph Build ID: 4009644c7dfb41957358d88f228b977b2fb552c7 Type: inuse_space Time: Apr 11, 2020 at 10:31pm (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 26.74GB, 98.87% of 27.05GB total Dropped 133 nodes (cum <= 0.14GB) Showing top 10 nodes out of 42 flat flat% sum% cum cum% 16.62GB 61.43% 61.43% 16.62GB 61.43% github.com/dgraph-io/dgraph/query.makeScalarNode 4.23GB 15.63% 77.06% 4.23GB 15.63% github.com/dgraph-io/dgraph/query.(*fastJsonNode).New 2.03GB 7.51% 84.57% 2.03GB 7.51% github.com/dgraph-io/dgraph/query.stringJsonMarshal 1.64GB 6.08% 90.65% 15.99GB 59.10% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListValue 0.88GB 3.25% 93.90% 5.24GB 19.36% github.com/dgraph-io/dgraph/query.(*fastJsonNode).SetUID 0.44GB 1.61% 95.52% 0.44GB 1.61% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListChild 0.36GB 1.32% 96.84% 0.39GB 1.45% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 0.25GB 0.92% 97.76% 0.25GB 0.92% github.com/dgraph-io/ristretto.newCmRow 0.16GB 0.6% 98.36% 0.16GB 0.6% github.com/dgraph-io/badger/v2/skl.newArena 0.14GB 0.51% 98.87% 0.14GB 0.51% github.com/dgraph-io/ristretto/z.(*Bloom).Size This PR profile: File: dgraph Build ID: 8fd737a95d4edf3ffb305638081766fd0044e99d Type: inuse_space Time: Apr 15, 2020 at 11:20am (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 15557.02MB, 98.53% of 15789.61MB total Dropped 168 nodes (cum <= 78.95MB) Showing top 10 nodes out of 60 flat flat% sum% cum cum% 6341.11MB 40.16% 40.16% 6341.11MB 40.16% github.com/dgraph-io/dgraph/query.(*encoder).appendAttrs 4598.84MB 29.13% 69.29% 4598.84MB 29.13% bytes.makeSlice 3591.16MB 22.74% 92.03% 3591.16MB 22.74% github.com/dgraph-io/dgraph/query.(*encoder).newNode 365.52MB 2.31% 94.34% 408.54MB 2.59% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 256MB 1.62% 95.97% 256MB 1.62% github.com/dgraph-io/ristretto.newCmRow 166.41MB 1.05% 97.02% 166.41MB 1.05% github.com/dgraph-io/badger/v2/skl.newArena 140.25MB 0.89% 97.91% 140.25MB 0.89% github.com/dgraph-io/ristretto/z.(*Bloom).Size 91.12MB 0.58% 98.48% 98.05MB 0.62% github.com/dgraph-io/dgraph/posting.(*List).Uids 6MB 0.038% 98.52% 122.23MB 0.77% github.com/dgraph-io/dgraph/worker.(*queryState).handleUidPostings.func1 0.64MB 0.004% 98.53% 387.17MB 2.45% github.com/dgraph-io/ristretto.NewCache
Fixes dgraph-io#5124, DGRAPH-1170 While converting a subgraph to JSON response, an intermediate data structure called fastJsonNode tree is formed. We have observed when response to be returned is big(specially in recurse queries), this datastructure itself can occupy lot of memory and leading to OOM in some cases. This PR aims to reduce space occupied by fastJsonNode tree. fastJsonNode tree is kind of n-ary tree, where each fastJsonNode maintains some meta data and list of its children. This PR tries to reduce space occupied by each node in following way: For each response a separate datastructure called encoder is formed which is responsible for maintaining meta data for all fastJsonNodes. encoder has metaSlice and childrenMap where all meta and children list are maintained for all fastJsonNodes. Index at which meta for a fastJsonNode is present, becomes its value and hence type of a fastJsonNode is uint32. meta for a fastJsonNode(present at int(fastJsonNode) value in metaSlice) is of uint64 type. It stores all the info for a fastJsonNode. Most significant bit stores value of List field, bytes 7-6 stores attr id and bytes 4 to 1 stores arena offset((explained below)). encoder has attrMap which has mapping of predicates to unique uint16 number. encoder also has arena. arena is a larger []byte, which stores bytes for each leaf node. It offsets are stored in fastJsonNode meta. arena stores same []byte only once and keeps a map for memhash([]byte) to offset mapping. On this change, I am able to run some of queries which were resulting in OOM current master. Profile for a query when RSS usage was around 30GB master profile on the query: File: dgraph Build ID: 4009644c7dfb41957358d88f228b977b2fb552c7 Type: inuse_space Time: Apr 11, 2020 at 10:31pm (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 26.74GB, 98.87% of 27.05GB total Dropped 133 nodes (cum <= 0.14GB) Showing top 10 nodes out of 42 flat flat% sum% cum cum% 16.62GB 61.43% 61.43% 16.62GB 61.43% github.com/dgraph-io/dgraph/query.makeScalarNode 4.23GB 15.63% 77.06% 4.23GB 15.63% github.com/dgraph-io/dgraph/query.(*fastJsonNode).New 2.03GB 7.51% 84.57% 2.03GB 7.51% github.com/dgraph-io/dgraph/query.stringJsonMarshal 1.64GB 6.08% 90.65% 15.99GB 59.10% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListValue 0.88GB 3.25% 93.90% 5.24GB 19.36% github.com/dgraph-io/dgraph/query.(*fastJsonNode).SetUID 0.44GB 1.61% 95.52% 0.44GB 1.61% github.com/dgraph-io/dgraph/query.(*fastJsonNode).AddListChild 0.36GB 1.32% 96.84% 0.39GB 1.45% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 0.25GB 0.92% 97.76% 0.25GB 0.92% github.com/dgraph-io/ristretto.newCmRow 0.16GB 0.6% 98.36% 0.16GB 0.6% github.com/dgraph-io/badger/v2/skl.newArena 0.14GB 0.51% 98.87% 0.14GB 0.51% github.com/dgraph-io/ristretto/z.(*Bloom).Size This PR profile: File: dgraph Build ID: 8fd737a95d4edf3ffb305638081766fd0044e99d Type: inuse_space Time: Apr 15, 2020 at 11:20am (IST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 15557.02MB, 98.53% of 15789.61MB total Dropped 168 nodes (cum <= 78.95MB) Showing top 10 nodes out of 60 flat flat% sum% cum cum% 6341.11MB 40.16% 40.16% 6341.11MB 40.16% github.com/dgraph-io/dgraph/query.(*encoder).appendAttrs 4598.84MB 29.13% 69.29% 4598.84MB 29.13% bytes.makeSlice 3591.16MB 22.74% 92.03% 3591.16MB 22.74% github.com/dgraph-io/dgraph/query.(*encoder).newNode 365.52MB 2.31% 94.34% 408.54MB 2.59% github.com/dgraph-io/dgraph/worker.(*queryState).handleValuePostings.func1 256MB 1.62% 95.97% 256MB 1.62% github.com/dgraph-io/ristretto.newCmRow 166.41MB 1.05% 97.02% 166.41MB 1.05% github.com/dgraph-io/badger/v2/skl.newArena 140.25MB 0.89% 97.91% 140.25MB 0.89% github.com/dgraph-io/ristretto/z.(*Bloom).Size 91.12MB 0.58% 98.48% 98.05MB 0.62% github.com/dgraph-io/dgraph/posting.(*List).Uids 6MB 0.038% 98.52% 122.23MB 0.77% github.com/dgraph-io/dgraph/worker.(*queryState).handleUidPostings.func1 0.64MB 0.004% 98.53% 387.17MB 2.45% github.com/dgraph-io/ristretto.NewCache
Dear contributor, what does this PR do?
Fixes #5124, DGRAPH-1170
Motivation
While converting a
subgraph
to JSON response, an intermediate data structure calledfastJsonNode
tree is formed. We have observed when response to be returned is big(specially in recurse queries), this datastructure itself can occupy lot of memory and leading to OOM in somecases.
This PR aims to reduce space occupied by
fastJsonNode
tree.fastJsonNode
tree is kind of n-arytree, where each
fastJsonNode
maintains some meta data and list of its children. This PR tries toreduce space occupied by each node in following way:
encoder
is formed which is responsible formaintaining meta data for all
fastJsonNodes
.encoder
hasmetaSlice
andchildrenMap
where all meta and children list are maintained forall
fastJsonNodes
. Index at which meta for afastJsonNode
is present, becomes its value and hence type of a fastJsonNode isuint32
.meta
for afastJsonNode
(present at int(fastJsonNode) value in metaSlice) is ofuint64
type. It stores all the info for afastJsonNode
. Most significant bit stores value ofList
field, bytes 7-6 stores attr id and bytes 4 to 1 storesarena
offset((explained below)).encoder
hasattrMap
which has mapping of predicates to uniqueuint16
number.arena
.arena
is a larger[]byte
, which stores bytes for each leaf node. Itoffsets are stored in
fastJsonNode
meta. arena stores same[]byte
only once and keeps a mapfor memhash([]byte) to offset mapping.
On this change, I am able to run some of queries which were resulting in OOM current master.
Components affected by this PR
Mostly query encoding/response.
Does this PR break backwards compatibility?
No
Fixes
More
Profile for a query when RSS usage was around 30GB
master profile on the query:
This PR profile:
Benchmarks for creating 2M empty fastJsonNodes:
Ran following benchmark on master:
Results:
Ran following benchmark on this PR:
Resutls:
This change is
Docs Preview: