Okay, good morning, everyone.
I want to talk about how we can dynamically change a load of the sort of front system
to be a better resident in clouds.
I did not move to IBM as part of the Reddit storage moving to IBM.
So this is still a Reddit presentation.
I don't have the Ceph logo here because it's generic presentation,
but it would be highly based on work that I did with others on Ceph.
So all the examples would be how this was implementing Ceph and how we could use it.
But the concepts are generic and not Ceph specific.
So it's a mix.
Okay, so we will talk, we want what is optimal cluster performance
and why we need optimal cluster performance.
It would be just at the beginning.
Then I'm going back to explain what we did in Ceph for Riff version.
We have a new read balancer which I explained quite shortly.
It's not into full details, but it's an infrastructure that could be used
to better control the load later.
Then some future plans that we already have which are good as examples
to what we could do with this infrastructure.
And then we'll go to the real problem,
how we could actually dynamically change the way the load is spread across Ceph cluster
in case we need to do it because other things change in the cluster.
How we could fit the way the load is spread in Ceph
because of some kind of external change in the conditions that we need to respond to.
So, again, just an example.
If we have a cluster, we have here nodes and we have three workloads
and they are split not totally evenly over the nodes.
And if we had bad luck, we could see that some nodes are more loaded than the other nodes.
And the problem is, as everyone probably guesses, one node reaches 100% load,
then the entire system starts to become slow because we have weakest link in the chain effect.
Assuming these workloads cannot respond fast enough,
so you get all kinds of cues created and the entire cluster loses its ability to perform well.
It still performs, but not that well.
So, basically, when we build a cluster and we look from the outside,
we want the load to be spread almost evenly.
So, when one node reaches 100%, we know the cluster is fully occupied.
There is nothing we could do about it.
This is another image which shows something which is way better
because the nodes, again, it's the same.
It's created the same number.
The areas of each workloads are the same.
And it actually shows the cluster itself is balanced.
And we could get, when it fills up, it fills up together,
so we use the cluster for the best that we can.
So, actually, if we try to look at what we have,
we want something flexible with fixed volume.
A balloon is what I find.
We want something, the performance that we want is the volume,
but it should be flexible because we can't control all the workloads.
So, if we take, for example, we have on the nodes a backup program
which runs in nights, but not at the same time.
It gradually goes over all the nodes.
So, each one of the nodes gets some kind of peak either in IOs
or in network traffic, and it peaks and it's more full than others.
And then the others, the other one, and it goes over all the nodes.
We can obviously mitigate this.
We can say, OK, we allocate some capacity for this backup program
so we know that other workloads could run on this node.
But if these backup programs work for an hour every day,
then we allocate some capacity for one hour and the other 23 hours,
it is not used.
Probably not used.
It's much better if we could incorporate this change,
these backup runs over the cluster and move the nodes of the nodes
with the backup to other nodes for some time for an hour.
Then the backup finishes go to another node,
then we move the nodes from back to the original node and we make it.
It's way more effective.
So, in some sense, we do some kind of over provisioning on the nodes
when we know that most of the time we are not over provisioning
and when we are over provisioning, we could mitigate this.
That's the idea.
And the other use case that we could have,
that we could get a node coming to full capacity when we didn't plan it,
it could be all kinds of failure, NIC problems,
all the top of the rack switch problems, other hardware,
or if we talk about SEPH, disk failure,
all kinds of other stuff could bring to a situation that may be bad
but not critical enough to take node down and do full rebuild and all this stuff.
So, that's the idea is to have something which is flexible and we could play with it.
The problem is that our balloon is built of LEGO bricks
and it's not as flexible as we think.
So, I want to show the amount of flexibility that we have,
that we could play with when we talk about software defined storage system
and which are more challenging than other workloads because it's stateful,
stateless workloads are easier to manage flexibly.
So, we want to go from this, this was the first,
this is the copy of the first diagram that I showed
and this is where we want to be and I change only the orange workload.
It's the same numbers, exactly the same numbers, exactly the same area,
orange area but split differently.
That's the approach that I want to take but, okay, it's a presentation.
I could do miracles in presentation, how I do it in real life
and I want to show how we could play assuming SEPH is or software defined storage
is the orange one, how we could play with this to a limits.
We can't do all the magic that we could do in presentation
but we could do reasonable, under some condition, very good work,
under other condition, improve the situation not to a perfect solution.
So, it's all based on the idea of the SEPH read balancer
and the idea today in SEPH, we have, not in SEPH, in every software defined storage,
the main balancing requirement is that all the disks are full at the same percentage.
The first disk which is full, the system is full.
So, this is our basic assumption.
Then, what we're doing today, we try to do is that if we have replica 3,
then we have XPGs mapped to OSD, X divided by 3 would be primaries.
So, the primaries are split evenly on the devices, not evenly split according to the number of the PGs.
So, if we have a device with more PGs, it has more primaries.
But, we don't have anything that does it, actually what is happening today in SEPH
is that we rely on the statistics of crash.
It works well for large clusters, it doesn't work well for small cluster.
So, the SEPH read balancer comes to fix this and it's what we currently have,
more in the next bullet, is for the small clusters where the statistics doesn't play well.
So, what we actually did,
we added three different things together, create a read balancer,
which could actually split the reads evenly across us.
This is actually what it does, it splits the primaries as I explained for replica X.
One divided by replica X are primaries per OSDs for the PGs.
So, it just changes the primaries.
So, first of all, we created, it would be part of the RIF version.
So, first of all, we created some kind of score.
The score represents how well the read is balanced versus the optimum.
Optimum is one.
If we have a score of two, meaning that under full load of reads,
the system would perform half of a good system.
If it's three, for example, we have replica three and the score is three.
And we have three disks and score is three, it means all the reads are for a single disk.
So, it's obviously third of the optimal when the reads split evenly among three disks.
So, that's what I have.
Score works really well when the read affinity of the devices is high and it's still monotonous,
but it's more difficult to explain the numbers when you have a lot of devices with small OSDs.
With small read affinity numbers, and I could explain later why it is,
it's not a good way to configure the system.
It creates all kinds of illegal configuration.
You have to do what the user asks you not to do, but that's for side code.
Then we have two new commands, PG-UPMAP commands, as we have PG-UPMAP primary and RMPG-UPMAP primary,
which actually say, okay, I get an OSD PG, the PG has X OSDs there,
make one of them the primary, I tell them which one.
So, I could actually change the primary within a PG.
And it's metadata only, we don't move data, you must give an OSD, which is part of the PG.
If you give OSD, which is not part of the PG, you get an error.
So, we are not trying to do anything, it's very cheap, very fast.
It just changes the order of the OSDs within the PG, that's everything that it does.
And since it's first version, we want to make it opt-in option.
So, we have a new command to the OSD MAP tool, which actually calculates everything and spits
a script file that you could run in order to get to the perfect distribution.
That's what we currently have.
So, this is the example of when you run this, you could see smaller clusters,
small cluster tend to be less balanced because statistics doesn't play well.
So, we have number of primaries for three OSDs, we have 11, 6, 15, obviously, it's not balanced.
And after you run it, you get 10, 11, 11, which is obviously the best that you could get.
And the number is not one, because the average is 10 and 2 thirds.
So, if you could have 10 and 2 thirds, you get a score of one, but it's 1.03 really good.
And here, obviously, it's 1.4 because it's 15 divided by 10 and 2 thirds.
This is the out file that you could actually run as a script in order to apply the changes.
If you look really carefully, you see that we have six changes, but actually we could only have five changes,
even maybe four, we could, we should just make this 10.
So, we have 11, 10, 11, that's because of its greedy algorithm.
Since these commands are really, really very, don't have overhead run really fast, it's not a problem.
If we would have need to move data, this would be a totally wrong one because we have two additional data movements.
We don't need it, it's really simple, so we didn't try to optimize on the number of changes just to make it come.
So, what is the implementation?
The implementation is, we have two functions.
One of them is, we have one, it's an example, calc desired primary distribution.
That's where we go over the configuration and decide what's the final distribution of primary that we want.
For OSD, how many primaries we want to be on this OSD?
It's really, it's some kind of policy function, it's really simple.
Today, it's, it does one divided by replica count, as I said, and it's about 40 lines of code, something really simple.
So, adding more policies just for, to understand if you want to add more policies and I'll talk about more policies immediately.
If you want to add more policies, then we're talking about tens of lines of code, nothing more than this.
Then we have the functions that actually takes the configuration and goes over all the PG's, it's all run by pool.
I didn't say this before, it's all run by pool because pool represents a workload and it's important to balance pair workload.
It goes over all the PG's on the pool and does its work and switches what it has to switch in order to get into the, what we calculated here.
So basically, this is function, this is infrastructure function that could be used, we could add more and I'll talk about more policies.
We could add more policies and adding the policy is really something simple and the command is fast.
That's, I keep on saying this because I want to talk about dynamic, that's what is behind the dynamic thing.
It's a cheap operation that we could run periodically and change things when we want to do it.
It's not involved in any, any high overhead or anything, it's just a metadata operation.
So we had the very simple read balancer, use case is small clusters, small clusters, it's very important use case for redhead,
now IBM, it's for ODF or for CEPH in OpenShift, but there is much more than we could do.
So now let's talk about the next, well, we have two use cases.
So what we could do more?
We have a mechanism, we could change the things, but it's a pretty strong mechanism and we want to use, to use it for further on.
So one thing and this is one thing, this is a use case for larger cluster, load, load balance better on heterogeneous system.
So you have a large CEPH cluster, you started to work five years ago, you have one terabyte devices, time comes, you need more capacity
and it doesn't make sense to buy one terabyte devices anymore and you bought two terabyte devices.
Now normally it means that you get twice the workload on the two terabyte devices because you need that the devices would be split, even filled evenly.
You could create policies that says that you change gradually and when the smaller devices are not full, you have the same capacity.
It doesn't work because every change is movement of data, it's very expensive.
So you could in theory create really, really nice policies in practice, the price you pay in order to keep it is too much.
Eventually you have device which is twice as large, it gets twice the capacity or you define it as a smaller device, then you lose capacity or it gets twice the load.
If it gets twice the load, the smaller devices are working on half the potential load.
So just for the numbers, I have a little exercise.
So we have devices of same technology, same bandwidth, same IOPS.
We have one OSD of two terabytes and four OSDs of one terabyte.
So it's like three OSDs of two terabytes if you think about capacity, so one of two terabytes, four of one terabyte.
So we have for each a PG, one copy on the large device and two copies on the small devices, a split.
So that's the assumption to just show a bit of play with the numbers.
And for the convenience, let's assume that we have 100 IOPS, it's just easy.
People like to think in round numbers.
So under full load, what happens?
This device goes to 100 IOPS because that's what it could provide.
And these devices get half the load and they would provide each 150 IOPS.
So total we have 300 IOPS.
And there is nothing we could actually do about it because the requirements to have the capacity split twice here than here is so strong that we can't change it, can't play with it.
That's the rules of the game.
Once we did it, we are bound in such a configuration.
Actually, we had a working cluster and we added one larger disk and all the cluster performs way, way worse than it used to be.
So it's not really something that we could do with it and accept, replace all the disks with larger disks or something like this.
But if you want to do gradual, we can do with it.
Now, there is something that we could do.
Let's assume that we have, for now, the read-only, just read-only load.
The capacity is still the same, 30 PG is 15, 15, 15.
But the 10 primaries here, I moved them and moved them here and split them here.
So we have here, when you have only reads, all the devices are fully working under full load.
So we moved from 300 IOPS to 500 IOPS, but just very minor change of changing primaries and splitting them differently across the cluster.
Now, this is the best that we could get.
If we have read-write, obviously, if we have write-only workload, we can't improve using this technique because this technique changes only reads.
So if we have write-only, we can't change, and if we have some writes, we can't get as good as this one because eventually this will get all the time, twice writes than the others.
So there is limitation to what we could do.
So the best potential is for read-only, but we could do also with mixed read-writes.
We could get a lot of improvement under full load, a lot.
So that's the idea, and this is already planned for the next version.
On purpose, we didn't put it on the first version because we want adoption of the feature, but this is, we plan to add two steps for the next version.
This is actually better supporting different sizes of devices.
So, can we improve on other loads which are not read-only?
And I already said this, we can do this, but in order to do this, in order to do good job, we need to understand per pool some characteristics of the workload.
And basically the read-write ratio.
If we get read-write ratio which is reasonably close to what we actually have, then we could do good improvement in the performance when we have different size devices and mixed workload.
Well, I said it, there are limitations what we could do, so I said one thing, we can't improve on write-only using this technique.
I've seen this, so it's real thing, but if you use replica three and we, instead of having one terabyte and two terabyte devices, we have one terabyte and five terabyte devices.
I've seen this in real life.
We can't improve, we can't, well, we can improve, but we can get to optimal performance when we have five terabyte devices and one terabyte devices on the same pool, the numbers are too low.
We could reduce it to the minimum, moving all reads out of the five terabyte devices, but eventually when you have enough writes, the system would not perform optimally.
So, we covered this.
So, another case which was, another case which is, well, it's a no-no, big no-no, don't put SSDs and HDDs on the same pool, everyone knows this.
Well, if you could, if you can make sure that you have enough SSDs that, sorry, that every PG is mapped to at least one SSD and then to HDDs, preferably replica three, one SSD, two HDDs, you could actually get the effect of read flash cache without cache misses.
You always read from the SSDs and you only write to the HDDs. You could improve on the performance of your system and get really fast read latencies.
So, I'm breaking this no-no, but it is important, don't mix the technologies if you can't make sure that all the reads are from the faster device.
If you could make, so if you take replica three and you put one-third of your PG's, of your SSDs are SSD capacity, one-third of the capacity is on SSDs, two-third on HDDs.
And you could also make sure, whatever techniques you use, it's really easy. You need to change a bit of crush rules, but all the copies, you have one copy on SSD and the other copies on HDDs.
You could actually improve your performance dramatically because the bottleneck of HDD would be only for writes and all the reads would get the SSD or NVMe or whatever performance.
So, I'm breaking one of the big no-no's here, but if you can't make sure that all the reads are from the fast devices, then you waste, it wouldn't work.
Eventually, under full load, you'll get the known weakest link in the chain and it would be blocked.
But if you can do it, so this is a way to modernize the devices gradually and not moving all the HDD to SSDs once, one-third in case of replica three could be the first step and you could do it gradually and you don't know to do anything.
So, that's another thing that could be with existing, by the way, for this, because it's different technique, you don't need what we did in the read balancer.
It's enough to have good crush rules. This could be managed by crush rules differently.
Okay, now we come to the dynamic aspect of this.
So, the thing is that we have cluster, we build cluster, we know we have the numbers, how the cluster performs, what are the network bandwidths, how devices perform.
It all works well until it doesn't.
So, we may have problems, hardware problems, and we may have noisy neighbors where we work.
As I said, full isolation of neighbors has a cost.
If you prevent over provisioning in all costs, well, it has a cost, depending on your workloads, you allocate a lot for temporary workloads.
So, in some cases, doing over provisioning of nodes makes sense if you know how to behave.
And this is especially in hyperconverged. So, we know that hyperconverged deployment, noisy neighbors, it could be in Kubernetes, we tend to limit, we know how to limit, we use, we tend to limit these CPUs and memories and not network.
Obviously, for sort of defense systems, the network is really important. So, noisy neighbor on the network could cause huge performance problems.
So, the process, and this is the process is, I want to explain, because it's more than just technical thing.
We want to monitor the IO performance from OSD level and up. We want to identify what happens. It could be on OSD level, it could be on node level, it could be on rack level.
We need to understand what happens. Then we could tune up the system. We could reduce the load. If the problem is temporary, we don't want to move data.
Even if the problem is that we have a faulty nick, and we know that it would take 24 hours to fix it, it may not be worth the effort of, for a node, to move all the data from this node to another node, to other nodes, to the rest of the cluster and then move it back.
If we could make sure that we have a faulty nick and until it is fixed, we don't read from this node at all. We just write to it. Maybe we could live with it.
So, the idea is that we don't want, the obvious solution is mark OSD is out and move the data and everything fix itself, but it costly, especially if you have a lot of data over the node.
So, this is a way to reduce the load temporarily until it is fixed. And then, once you did all your magic, fixed everything, you could go back to the normal.
So, and by the way, this is something that is not related to software-defined storage, but it's much easier, all this much easier to do for a stateless application. If we have a web server that gives us the stock exchange quotes, and it doesn't function, one of the servers doesn't function, we change the proxy and we send the request to other servers.
It's way more difficult to do with stateful application when you can't exchange every server with every other server and you need, there are more limitations and obviously we talk about storage. It's the most stateful thing that you could think of.
So, option one, it's something we thought about even for the rebalances we did. This is what is a very good solution for the stateless part, it's called the power of two.
Before you send the request, you randomly select two candidates to get the request, you find out who is more loaded and you send to the one that is less loaded. It does magic.
That's really, really good way to move the load from the loaded servers to the unloaded servers and it works. Unfortunately, in order to fix, to do such thing in SEF, you need to go into the data path. Everything that I explained up until now is totally outside of the data path.
You have to add things to the data path and to change the clients and we thought about this also in order to do the read balancing, it would be very simple. Every PG, since we have read from non-replicant in SEF now, we could say for every PG, don't send the request to the primary, send it randomly to any of the PG's and automatically you'll get the balance spread.
Sorry, for HPG to any of the OSD's, don't send to the primary OSD, just send to whatever you have there. We decided not to do it, it's risky and we don't like to play with the data path. I don't like to play with the data path personally, but it was a mutual decision, not only me.
So that's option two, monitor centrally, monitor centrally, obviously, create the policy. This is something that you need to get the function of the policy that knows what to do when you find these discrepancies.
You need to understand what you're doing, what you want to achieve, how much time you want to play with this before you decide to move data or maybe you need the human involvement which will tell me, okay, I'm going to fix this, don't do anything.
It's a policy you need to do both in the terms of workflow and then program what you need to program for this.
The policy function is small, we talked about this, it's nothing. And when the performance changes, first you need to notify operator because we suspect that a lot of the problems could be hardware problems that should be fixed and we need to tell something that we see something bad.
And then change primary settings so we remove the load from the less performant OSDs or components to other places.
And before monitoring the system and when it's back to normal, we could remove everything and go back to full things. So here is the conclusion, again, it's data path outside of the data path, syncing the metadata, the performance to the clients, which is also something that we didn't want to do,
versus the external metadata configuration that we do on the server side, whatever, because we trigger the policy from server side and no change.
So that's the idea for, again, this is for how to implement this in the future, but the idea we could, if we measure, if we know what's going on, we could improve to send it some point aside whether this improvement is good enough for us until we fix,
or in some cases we decide to move data and don't say don't move data, but don't move it as the default option for every problem.
Acknowledgements, Orit, which worked with me a lot on the ideas that I put here, and she's now in IBM.
And Laura, which did a lot of the coding with me on this, and actually since my coding skills were a bit rusty, I couldn't do a lot without her.
So thanks to Orit and Laura that helped in this project, and I'm done. Questions?
Yes, please. Can you use the new OSD map rule that is like released for brief, as I said, like master? Can you use that to generate a list of distribution of the primaries that will be optimal on all the clusters,
and use the temporary map feature that is already existing in all our releases to actually apply that policy that this would be optimal.
Okay.
Basically, I'd like to back forward, but then without.
The question was, if you could use the OSD map tool on older classes, and then instead of using the new PG op map use a temporary permit permit temp in order to do it.
I think it should, the OSD, you should have a new set cluster with all the new tools because there are some other changes, but you run OSD map tools on a file that generated from the OSD map tools work on configuration file that you take.
So you could create a configuration file out of an old cluster and run it with the new OSD map tool. And then so I think actually the primary temp is how we tested this.
What is missing is that the new OSD map tool relates on the score. And actually I'm not sure, it not depends on the score, but it uses a score internally. So it should run on a new environment, but I think it should be able to work.
I have my information here, or I'll give you my email and send me, I'll verify this. It should work. I'm not 100% sure, but it should work.
If it doesn't work, it is a problem. You said that you defined that it's especially good for smaller clusters. How big would you define smaller clusters?
See, the question was, what is small cluster and big cluster? The thing is, the way CRUSH works, it uses statistics to do the split to primaries.
If you have a cluster in which primaries are not balanced, probably it falls into the smaller side. But if you look at hundreds of OSD, even it's a number of PG's, not number of OSD's.
In the past, I did an experiment and I saw that you saw the score here, 1.4. Every time that you double the number of the PG's, roughly the difference from 1 goes by half.
So we put 1.4 for 32 PG's, probably around 1.2 for 64. So it's large clusters with pools with large number of PG's, usually somehow balance themselves.
But it's a matter of, you need to look, if you have unbalanced pools, it's unbalanced pools. It doesn't matter which cluster you are in.
But that's why we do it. But most benefit for the larger pools, the pools with the data, is when you have smaller clusters, it's not worth putting 512 PG's per pool and you work with smaller numbers.
If you have 510K or 2K PG's per cluster, probably your score would be pretty good.
Would it also be useful for erasure coded pools?
The question whether it's good for erasure coded pools, probably not. Well, way, way, way less sufficient. I tried to do the theory behind it.
Then maybe really, really little, I don't know. So currently we check in the code, it doesn't work for erasure coded at all because we think it doesn't worth the hassle.
So it's tested and it works only on replicables.
Okay, my time is up. Thank you very much. It was pleasure being face to face here. Thank you.