Skip to content
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

Add free() for raft node #423

Merged
merged 2 commits into from Jul 23, 2021
Merged

Add free() for raft node #423

merged 2 commits into from Jul 23, 2021

Conversation

jayzhan211
Copy link
Contributor

@jayzhan211 jayzhan211 commented Mar 18, 2021

Close #419 and #448 .

Add two API

  1. free_resources(id: u64)
  2. free_resources_for_all()

Signed-off-by: jayzhan jayzhan211@gmail.com

@jayzhan211
Copy link
Contributor Author

how to add test? or not

/// free buffer memory
#[inline]
pub fn shrink(&mut self) {
self.buffer.shrink_to_fit();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After shrink the capacity will change, the size for determine full() will be effected, is it fine?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's wrong. It should be able to reallocate when new messages need to be sent.

src/raft.rs Outdated
@@ -2678,4 +2678,27 @@ impl<T: Storage> Raft<T> {
pub fn uncommitted_size(&self) -> usize {
self.uncommitted_state.uncommitted_size
}

/// Free resources for all the raft node
pub fn free_resources_for_all(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just this method is enough. Choosing target id is too verbose.

@BusyJay
Copy link
Member

BusyJay commented Mar 18, 2021

Tests should be added to check if flow control still work as expected before and after calling shrink.

@jayzhan211 jayzhan211 marked this pull request as draft March 18, 2021 09:10
@jayzhan211 jayzhan211 marked this pull request as ready for review March 26, 2021 04:39
@jayzhan211 jayzhan211 requested a review from BusyJay March 26, 2021 07:58
@jayzhan211 jayzhan211 marked this pull request as draft March 27, 2021 01:21
@jayzhan211 jayzhan211 marked this pull request as ready for review March 27, 2021 01:30
pub fn free_mem(&mut self) {
let mut v = Vec::with_capacity(self.count);
for i in 0..self.count {
v.push(self.buffer[self.start + i])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get it. v doesn't seem to be used, and why append the element from buffer to v? On the other hand, self.buffer should be dropped to free memory.

Copy link
Contributor Author

@jayzhan211 jayzhan211 Apr 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to re-create an buffer with length & capacity equal to count, in order to decrease the allocated memory,
and remain only from start to start + count - 1

On the other hand, buffer should be dropped to free memory

If count is zero, buffer will be Vec with capacity 0, I am not sure what is the implementation inside, but I guess there is no memory allocated?
If count is positive, we should at least remain count sized Vec (?

Copy link
Contributor Author

@jayzhan211 jayzhan211 Apr 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shrink_to_fit that mentioned before, I think it will do nothing if the Vec is full once

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the issue left related to the memory problem? Is #10156?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is 10156?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, is this tikv/tikv#10156, found it under tikv/rfcs#59

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @hicqu

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The left issue is unchanged for raft-rs. Memory usage of Inflight is too large if there are lots of raft peers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BusyJay do you mean free_mem should only free empty inflights, but keep no-empty inflights unchanged?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have discussed with Jay. He thinks the current implemenatation is OK. BTW how about using vec.extend_from_slice instead?

@hicqu
Copy link
Contributor

hicqu commented Jun 28, 2021

How about lazily allocate memory instead of adding free_* functions? @jayzhan211

@hicqu
Copy link
Contributor

hicqu commented Jun 29, 2021

How about this solution:
for an given Inflight,

  • allocate memory only if necessary
  • reallocate memory if more items are pushed
  • free and malloc again after free_to is called, and shrink the buffer if necessary

Or this:

  • Allocate link-list nodes on heap, every node carries 16 u64 numbers

Any suggestions @BusyJay


/// reallocate buffer memory
#[inline]
pub fn reallocate(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress should reallocate when necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, buffer will be reallocated when you call this function, what is other situation when we need reallocation?

@hicqu
Copy link
Contributor

hicqu commented Jul 5, 2021

@jayzhan211 we can try this solution:

  • free_mem shouldn't re-allocate memory. So for an un-empty Inflights, we can

    1. leave it unchanged, or
    2. Call shrink_to_fit for it

    Both of them are OK.

  • Inflights::reallocate is not necessary. Memory can be allocated when Inflights::add is called.

  • Call free_mem when a peer become follower.

@BusyJay please help to confirm whether it fits with your design or not.

@hicqu
Copy link
Contributor

hicqu commented Jul 9, 2021

@jayzhan211 any updates?

@jayzhan211
Copy link
Contributor Author

@jayzhan211 any updates?

Does it fit @BusyJay 's design

@BusyJay
Copy link
Member

BusyJay commented Jul 9, 2021

@jayzhan211 we can try this solution:

  • free_mem shouldn't re-allocate memory. So for an un-empty Inflights, we can

    1. leave it unchanged, or
    2. Call shrink_to_fit for it

    Both of them are OK.

  • Inflights::reallocate is not necessary. Memory can be allocated when Inflights::add is called.

  • Call free_mem when a peer become follower.

@BusyJay please help to confirm whether it fits with your design or not.

LGTM, thanks!

@jayzhan211
Copy link
Contributor Author

I have difficult for adding test (not famailiar with APIs).
Maybe add it afterall.

let len = self.buffer.len();
for _ in self.start + self.count..len {
self.buffer.pop();
}
Copy link
Contributor Author

@jayzhan211 jayzhan211 Jul 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We initialize the buffer with fixed capacity, so we need to remove element for shrink to fit to reduce allocated memory, any better ways?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about don't touch it if it's not empty? So the resource can be freed later, and no allocations are introduced.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about don't touch it if it's not empty?

I am not sure how frequently the buffer is empty. If it happens a lot, this approach is good enough.

If most of the time the buffer is non-empty but small, then memory might be wasted (0..start - 1).

// .expect("");
// r.read_messages();
// }
//
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this line, inflights 2 (follower) is filled with 10 element, inflights 1 (leader) is empty, so it seems that we need to transfer leader from 2 to 1, then transfer 1 to 2 again, so peer 2 will call free_mem ?

hicqu added a commit to hicqu/raft-rs that referenced this pull request Jul 19, 2021
Signed-off-by: qupeng <qupeng@pingcap.com>
@hicqu
Copy link
Contributor

hicqu commented Jul 19, 2021

@jayzhan211 I have send a patch PR to your code. Could you take a look?

jayzhan211 pushed a commit to jayzhan211/raft-rs that referenced this pull request Jul 20, 2021
Signed-off-by: qupeng <qupeng@pingcap.com>
hicqu
hicqu previously approved these changes Jul 20, 2021
r.step(resp).unwrap();

// Test `maybe_free_inflight_buffers` works as expected.
r.maybe_free_inflight_buffers();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the case can pass even the function does nothing.

@@ -35,6 +38,7 @@ impl Clone for Inflights {
start: self.start,
count: self.count,
buffer,
cap: self.cap,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L36 can be also removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buffer must have it's capacity set correctly on clone, normally it does not.

If it is not true, I think we can use derive(Clone) for inflight

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that capacity is maintained explicitly, so the statement is not valid anymore. derive(Clone) is a good idea!.


/*
1: cap=0/start=0/count=0/buffer=[]
2: cap=256/start=0/count=1/buffer=[2]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

count should be 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why MsgPropose sends 2 to others, but it did.

resp.index = r.raft_log.last_index();
r.step(resp).unwrap();

for (&id, pr) in r.prs().iter() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop is not necessary if only tests id == 2.

@jayzhan211
Copy link
Contributor Author

I will use buffer.capacity, not cap(), because I found that we initialize buffer with cap

@jayzhan211
Copy link
Contributor Author

@hicqu PTAL

hicqu
hicqu previously approved these changes Jul 21, 2021
@hicqu
Copy link
Contributor

hicqu commented Jul 21, 2021

@jayzhan211 seems some commits break the DCO check. Could you rebase and squash the PR like this:

git checkout free -b backup-free
git branch -D free
git checkout master -b free
git merge backup-free --no-commits --no-logs --squash
git commit -s     # then cleanup squashed commit logs

Signed-off-by: jayzhan211 <jayzhan211@gmail.com>
Copy link
Member

@BusyJay BusyJay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hicqu you may need a method to monitor the memory correctly.


/// Number of inflight messages.
#[inline]
pub fn count(&self) -> usize {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making it doc hiddern as it's only used in tests?

@hicqu
Copy link
Contributor

hicqu commented Jul 21, 2021

you may need a method to monitor the memory correctly.

@BusyJay Do you mean we need fn Raft::inflight_buffers_size?

@jayzhan211
Copy link
Contributor Author

you may need a method to monitor the memory correctly.

@BusyJay Do you mean we need fn Raft::inflight_buffers_size?

Add a function that return buffer.capacity()?

@BusyJay
Copy link
Member

BusyJay commented Jul 22, 2021

@BusyJay Do you mean we need fn Raft::inflight_buffers_size?

Yes, something like that.

@jayzhan211
Copy link
Contributor Author

jayzhan211 commented Jul 22, 2021

the buffer size is count, the buffer capacity is either 0 or cap

@hicqu
Copy link
Contributor

hicqu commented Jul 22, 2021

@jayzhan211 we need to add a new method for Raft:

+
+    /// Get the inflight buffer size.
+    pub fn inflight_buffers_size(&self) -> usize {
+        let mut total_size = 0;
+        for (_, pr) in self.prs().iter() {
+            total_size += pr.ins.buffer.capacity();
+        }
+        total_size
+    }

to get the allocated buffer size for a raft peer.

Signed-off-by: jayzhan211 <jayzhan211@gmail.com>
@jayzhan211 jayzhan211 requested a review from BusyJay July 22, 2021 12:42
Copy link
Member

@BusyJay BusyJay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@hicqu hicqu merged commit fa0a7c8 into tikv:master Jul 23, 2021
@jayzhan211
Copy link
Contributor Author

Thanks! @hicqu @BusyJay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize memory footprint of raft state machine
3 participants