ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled
@ 2024-04-22 16:02 eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-04-22 16:45 ` [ruby-core:117645] " byroot (Jean Boussier) via ruby-core
                   ` (23 more replies)
  0 siblings, 24 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-04-22 16:02 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been reported by eightbitraptor (Matthew Valentine-House).

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```ruby out_of_band do GC.start if GC.needs_major?  end ```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a 9% reduction in average and p99 response times when compared against
standard GC (4% p99.9 and p99.99).

<img width="1000" alt="Screenshot 2024-04-22 at 16 55 53"
src="https://github.com/ruby/ruby/assets/31869/8a80c102-1564-4bc9-ba44-6e9a8b85f971">


This drops slightly to an 8% reduction in average and p99 response times when
compared against standard OOBGC (3.59 p99.9 and 4% p99.99)

<img width="1000" alt="Screenshot 2024-04-22 at 16 56 10"
src="https://github.com/ruby/ruby/assets/31869/1baef7ea-0155-4ff9-8ba4-a967b75749fe">







-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117645] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-04-22 16:45 ` byroot (Jean Boussier) via ruby-core
  2024-04-23 22:43 ` [ruby-core:117664] " nateberkopec (Nate Berkopec) via ruby-core
                   ` (22 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: byroot (Jean Boussier) via ruby-core @ 2024-04-22 16:45 UTC (permalink / raw)
  To: ruby-core; +Cc: byroot (Jean Boussier)

Issue #20443 has been updated by byroot (Jean Boussier).

File Capture d’écran 2024-04-22 à 18.41.52.png added

To add a little bit more context on @eightbitraptor's description. In some graph you see 3 groups:

  - `oobgc-off`: which is workers without any Out of Band GC.
  - `oobgc-on`: which is workers with our previous OOB GC implementation (once every 128 to 512 requests, 20% more every time)
  - `oobgc-disable-major`: which is the new OOB GC implementation that only run GC when `GC.need_major?` returns true.

The new implementation not only improve latency in most case, it also reduce the capacity impact of having workers running major GC when it wasn't needed.

Here is the graph of `GC.stat[:major_gc_count]` over these 3 groups, and as you can see `oobgc-disable-major` runs major GC only about as often as no-OOBGC, whereas the previous implementation is triggering more often than actually needed, wasting server capacity.

![](Capture%20d%E2%80%99e%CC%81cran%202024-04-22%20a%CC%80%2018.41.52.png)




----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108055

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```ruby out_of_band do GC.start if GC.needs_major?  end ```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a 9% reduction in average and p99 response times when compared against
standard GC (4% p99.9 and p99.99).

<img width="1000" alt="Screenshot 2024-04-22 at 16 55 53"
src="https://github.com/ruby/ruby/assets/31869/8a80c102-1564-4bc9-ba44-6e9a8b85f971">


This drops slightly to an 8% reduction in average and p99 response times when
compared against standard OOBGC (3.59 p99.9 and 4% p99.99)

<img width="1000" alt="Screenshot 2024-04-22 at 16 56 10"
src="https://github.com/ruby/ruby/assets/31869/1baef7ea-0155-4ff9-8ba4-a967b75749fe">





---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117664] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-04-22 16:45 ` [ruby-core:117645] " byroot (Jean Boussier) via ruby-core
@ 2024-04-23 22:43 ` nateberkopec (Nate Berkopec) via ruby-core
  2024-04-24 10:17 ` [ruby-core:117676] " eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (21 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: nateberkopec (Nate Berkopec) via ruby-core @ 2024-04-23 22:43 UTC (permalink / raw)
  To: ruby-core; +Cc: nateberkopec (Nate Berkopec)

Issue #20443 has been updated by nateberkopec (Nate Berkopec).


Regarding the interface:

``` ruby
GC.disable(major: true)
GC.disable(type: :major)
```

Should we consider these additional keyword arguments rather than adding a new method?




----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108072

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117676] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-04-22 16:45 ` [ruby-core:117645] " byroot (Jean Boussier) via ruby-core
  2024-04-23 22:43 ` [ruby-core:117664] " nateberkopec (Nate Berkopec) via ruby-core
@ 2024-04-24 10:17 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-04-24 11:57 ` [ruby-core:117682] " byroot (Jean Boussier) via ruby-core
                   ` (20 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-04-24 10:17 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


nateberkopec (Nate Berkopec) wrote in #note-4:
> Regarding the interface:
> 
> ``` ruby
> GC.disable(major: true)
> GC.disable(type: :major)
> ```
> 
> Should we consider these additional keyword arguments rather than adding a new method?


I slightly prefer having a new method pair for this, however I don't object to changing it.

I do have a slight concern that `GC.disable(major: true)` could be read either as disabling major GC's or keeping majors enabled and disabling minors

So if we decide to use the keyword approach I prefer `GC.disable(type: major)`



----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108086

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117682] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (2 preceding siblings ...)
  2024-04-24 10:17 ` [ruby-core:117676] " eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-04-24 11:57 ` byroot (Jean Boussier) via ruby-core
  2024-04-24 14:56 ` [ruby-core:117687] " shan (Shannon Skipper) via ruby-core
                   ` (19 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: byroot (Jean Boussier) via ruby-core @ 2024-04-24 11:57 UTC (permalink / raw)
  To: ruby-core; +Cc: byroot (Jean Boussier)

Issue #20443 has been updated by byroot (Jean Boussier).


> I slightly prefer having a new method pair for this

Same. it makes it easy to test for existence with `respond_to?` and alternative implementations can make them undefined methods like for `Process.fork` etc.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108092

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117687] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (3 preceding siblings ...)
  2024-04-24 11:57 ` [ruby-core:117682] " byroot (Jean Boussier) via ruby-core
@ 2024-04-24 14:56 ` shan (Shannon Skipper) via ruby-core
  2024-04-25  0:59 ` [ruby-core:117695] " ko1 (Koichi Sasada) via ruby-core
                   ` (18 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: shan (Shannon Skipper) via ruby-core @ 2024-04-24 14:56 UTC (permalink / raw)
  To: ruby-core; +Cc: shan (Shannon Skipper)

Issue #20443 has been updated by shan (Shannon Skipper).


I wonder if "full_sweep" be worth considering as an alternative to "major" to align with the existing `GC.start(full_sweep: true)` keyword argument? Or they could be aliased, but it seems nice to be consistent if I'm understanding it correctly that they have equivalent meaning.

``` ruby
GC.start(full_sweep: true) # existing default
GC.enable_full_sweep
GC.disable_full_sweep
```

or would a setter make sense?

```ruby
GC.full_sweep?
#=> true

GC.full_sweep = false

GC.full_sweep?
#=> false
``` 

For checking if a full sweep or major is needed, would the addressing the Object convention mean `GC.need_full_sweep?` singular, `Dir.exist?` style? Or a plain `GC.full_sweep?` seems nice.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108096

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117695] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (4 preceding siblings ...)
  2024-04-24 14:56 ` [ruby-core:117687] " shan (Shannon Skipper) via ruby-core
@ 2024-04-25  0:59 ` ko1 (Koichi Sasada) via ruby-core
  2024-04-25  1:12 ` [ruby-core:117696] " ko1 (Koichi Sasada) via ruby-core
                   ` (17 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: ko1 (Koichi Sasada) via ruby-core @ 2024-04-25  0:59 UTC (permalink / raw)
  To: ruby-core; +Cc: ko1 (Koichi Sasada)

Issue #20443 has been updated by ko1 (Koichi Sasada).


Basically I like this idea. Some points.

* should not use "major" as a "major gc", so `GC.disable_major` should be `GC.disable_major_gc` and so on.
* I don't have strong opinion about `GC.disable(major_gc: true)` or `GC.disable_major_gc`
* "When major GC's are disabled, object promotion is disabled" what happens on oldgen->younggen references? points from the remembers set? I think we can promote this case because it makes minor gc faster (the promoted objects can not be freed until major gc, so the number of living objects is same).

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108104

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117696] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (5 preceding siblings ...)
  2024-04-25  0:59 ` [ruby-core:117695] " ko1 (Koichi Sasada) via ruby-core
@ 2024-04-25  1:12 ` ko1 (Koichi Sasada) via ruby-core
  2024-04-25  3:44 ` [ruby-core:117699] " duerst via ruby-core
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: ko1 (Koichi Sasada) via ruby-core @ 2024-04-25  1:12 UTC (permalink / raw)
  To: ruby-core; +Cc: ko1 (Koichi Sasada)

Issue #20443 has been updated by ko1 (Koichi Sasada).


* `needs_major` "s" should not be on method name (like `File.exists` -> `File.exist`)
* can you measure the memory consumption? It is a key compared with old OOBGC.


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108105

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117699] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (6 preceding siblings ...)
  2024-04-25  1:12 ` [ruby-core:117696] " ko1 (Koichi Sasada) via ruby-core
@ 2024-04-25  3:44 ` duerst via ruby-core
  2024-04-25  6:15 ` [ruby-core:117700] " byroot (Jean Boussier) via ruby-core
                   ` (15 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: duerst via ruby-core @ 2024-04-25  3:44 UTC (permalink / raw)
  To: ruby-core; +Cc: duerst

Issue #20443 has been updated by duerst (Martin Dürst).


ko1 (Koichi Sasada) wrote in #note-8:
> Basically I like this idea. Some points.
> 
> * should not use "major" as a "major gc", so `GC.disable_major` should be `GC.disable_major_gc` and so on.

Isn't the `gc` already very obvious from the class `GC`?



----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108108

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117700] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (7 preceding siblings ...)
  2024-04-25  3:44 ` [ruby-core:117699] " duerst via ruby-core
@ 2024-04-25  6:15 ` byroot (Jean Boussier) via ruby-core
  2024-05-09 12:56 ` [ruby-core:117821] " matz (Yukihiro Matsumoto) via ruby-core
                   ` (14 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: byroot (Jean Boussier) via ruby-core @ 2024-04-25  6:15 UTC (permalink / raw)
  To: ruby-core; +Cc: byroot (Jean Boussier)

Issue #20443 has been updated by byroot (Jean Boussier).


> what happens on oldgen->younggen references? points from the remembers set?

Yes.

> I think we can promote this case because it makes minor gc faster (the promoted objects can not be freed until major gc, so the number of living objects is same).

I understand your point, but I fear it could be counter-productive. We specifically stopped doing that in [Feature #19678] because there is many patterns in common Ruby code bases that are causing promotion.

I'd rather run **minor GC** out of band frequently, and **major GC** out of band very rarely, because the ratio of effectively permanent objects to ephemeral ones tend to be large in long running applications.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108110

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117821] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (8 preceding siblings ...)
  2024-04-25  6:15 ` [ruby-core:117700] " byroot (Jean Boussier) via ruby-core
@ 2024-05-09 12:56 ` matz (Yukihiro Matsumoto) via ruby-core
  2024-05-09 20:29 ` [ruby-core:117824] " eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (13 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: matz (Yukihiro Matsumoto) via ruby-core @ 2024-05-09 12:56 UTC (permalink / raw)
  To: ruby-core; +Cc: matz (Yukihiro Matsumoto)

Issue #20443 has been updated by matz (Yukihiro Matsumoto).


I am neutral on this proposal. However, I am concerned that there may not be a Major GC when GC made pluggable.

Matz.


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108230

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117824] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (9 preceding siblings ...)
  2024-05-09 12:56 ` [ruby-core:117821] " matz (Yukihiro Matsumoto) via ruby-core
@ 2024-05-09 20:29 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-05-12  6:49 ` [ruby-core:117843] " byroot (Jean Boussier) via ruby-core
                   ` (12 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-05-09 20:29 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


matz (Yukihiro Matsumoto) wrote in #note-12:
> I am neutral on this proposal. However, I am concerned that there may not be a Major GC when GC made pluggable.
> 
> Matz.

I think this is still relevant when GC is pluggable. Ruby will always ship with the existing GC by default, and there will always be applications for which running standard OOBGC will be the best approach.

I would anticipate that this function would warn or become a no-op when a pluggable GC module is in use.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108233

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117843] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (10 preceding siblings ...)
  2024-05-09 20:29 ` [ruby-core:117824] " eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-05-12  6:49 ` byroot (Jean Boussier) via ruby-core
  2024-05-14  7:23 ` [ruby-core:117871] " matz (Yukihiro Matsumoto) via ruby-core
                   ` (11 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: byroot (Jean Boussier) via ruby-core @ 2024-05-12  6:49 UTC (permalink / raw)
  To: ruby-core; +Cc: byroot (Jean Boussier)

Issue #20443 has been updated by byroot (Jean Boussier).


I think that same concern applies to some existing `GC` methods (e.g. `GC.compact`, `GC.verify_compaction_reference`) etc. So it would make sense that plugable GC would offer a way for the GCs to register behavior for these methods, and if they don't it either noop or acts as not implemented (`respond_to? -> false` etc).

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108248

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:117871] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (11 preceding siblings ...)
  2024-05-12  6:49 ` [ruby-core:117843] " byroot (Jean Boussier) via ruby-core
@ 2024-05-14  7:23 ` matz (Yukihiro Matsumoto) via ruby-core
  2024-06-03 18:26 ` [ruby-core:118156] " ko1 (Koichi Sasada) via ruby-core
                   ` (10 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: matz (Yukihiro Matsumoto) via ruby-core @ 2024-05-14  7:23 UTC (permalink / raw)
  To: ruby-core; +Cc: matz (Yukihiro Matsumoto)

Issue #20443 has been updated by matz (Yukihiro Matsumoto).


As long as the pluggable GC issue is clearly stated somewhere, I have no objection.

Matz.


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108283

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118156] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (12 preceding siblings ...)
  2024-05-14  7:23 ` [ruby-core:117871] " matz (Yukihiro Matsumoto) via ruby-core
@ 2024-06-03 18:26 ` ko1 (Koichi Sasada) via ruby-core
  2024-06-03 18:40 ` [ruby-core:118157] " ko1 (Koichi Sasada) via ruby-core
                   ` (9 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: ko1 (Koichi Sasada) via ruby-core @ 2024-06-03 18:26 UTC (permalink / raw)
  To: ruby-core; +Cc: ko1 (Koichi Sasada)

Issue #20443 has been updated by ko1 (Koichi Sasada).


byroot (Jean Boussier) wrote in #note-11:
> > I think we can promote this case because it makes minor gc faster (the promoted objects can not be freed until major gc, so the number of living objects is same).
> 
> I understand your point, but I fear it could be counter-productive. We specifically stopped doing that in [Feature #19678] because there is many patterns in common Ruby code bases that are causing promotion.
> 
> I'd rather run **minor GC** out of band frequently, and **major GC** out of band very rarely, because the ratio of effectively permanent objects to ephemeral ones tend to be large in long running applications.

I understand the proposed code:

* if old object `a` refers to young object `b`, put `a` into a remember set.
* if the `a->b` reference leaves, `a` will be in a remember set.
* if the `a->b` reference was lost (`a.b = nil` for example), `b` will be free'ed and `a` is not in a remember set.

If there is no more permanent objects, it works well as #19678.
And on the web requests, most of objects will be free'ed after the request processing (other than cached objects).

I understand the logic so no objection here.


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108594

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118157] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (13 preceding siblings ...)
  2024-06-03 18:26 ` [ruby-core:118156] " ko1 (Koichi Sasada) via ruby-core
@ 2024-06-03 18:40 ` ko1 (Koichi Sasada) via ruby-core
  2024-06-03 21:24 ` [ruby-core:118161] " eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (8 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: ko1 (Koichi Sasada) via ruby-core @ 2024-06-03 18:40 UTC (permalink / raw)
  To: ruby-core; +Cc: ko1 (Koichi Sasada)

Issue #20443 has been updated by ko1 (Koichi Sasada).


Proposed code just ignore `gc_aging` while "disable_major_gc" but it can increase aging up to 2 and next major gc makes the age 2 object to old object. In other words permanent objects can promote on single major gc easily.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108595

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118161] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (14 preceding siblings ...)
  2024-06-03 18:40 ` [ruby-core:118157] " ko1 (Koichi Sasada) via ruby-core
@ 2024-06-03 21:24 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-06-04  8:21 ` [ruby-core:118167] " eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-06-03 21:24 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


Thanks @ko1. I've updated the documentation as per @matz request, so I'll merge and close this now.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108599

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118167] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (15 preceding siblings ...)
  2024-06-03 21:24 ` [ruby-core:118161] " eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-06-04  8:21 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-06-06  8:40 ` [ruby-core:118198] " matz (Yukihiro Matsumoto) via ruby-core
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-06-04  8:21 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


eightbitraptor (Matthew Valentine-House) wrote in #note-18:
> Thanks @ko1. I've updated the documentation as per @matz request, ~~so I'll merge and close this now.~~

I've discussed this further with @ko1 on Slack, who would like the names clarified at the next dev meeting. So this ticket will remain open for now.



----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108608

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118198] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (16 preceding siblings ...)
  2024-06-04  8:21 ` [ruby-core:118167] " eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-06-06  8:40 ` matz (Yukihiro Matsumoto) via ruby-core
  2024-06-10 11:23 ` [ruby-core:118265] " eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (5 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: matz (Yukihiro Matsumoto) via ruby-core @ 2024-06-06  8:40 UTC (permalink / raw)
  To: ruby-core; +Cc: matz (Yukihiro Matsumoto)

Issue #20443 has been updated by matz (Yukihiro Matsumoto).


First, If you clearly define what would happen when the (plugged) GC does not generational scanning, I am OK for it. @byroot told me that calling those methods would raise `NotImplementedError` when GC does not provide generational GC.

For API, I don't like the name `needs_major?`. At least, it should be `need_major?` to follow Ruby's naming convention (no third-person singular present).

And in the developers' meeting, @byroot proposed `GC.config(full_mark: true)`. How do you think?

Matz.




----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108685

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118265] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (17 preceding siblings ...)
  2024-06-06  8:40 ` [ruby-core:118198] " matz (Yukihiro Matsumoto) via ruby-core
@ 2024-06-10 11:23 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-07-08 17:47 ` [ruby-core:118489] " ko1 (Koichi Sasada) via ruby-core
                   ` (4 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-06-10 11:23 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


matz (Yukihiro Matsumoto) wrote in #note-20:
> First, If you clearly define what would happen when the (plugged) GC does not generational scanning, I am OK for it. @byroot told me that calling those methods would raise `NotImplementedError` when GC does not provide generational GC.
> 
> For API, I don't like the name `needs_major?`. At least, it should be `need_major?` to follow Ruby's naming convention (no third-person singular present).
> 
> And in the developers' meeting, @byroot proposed `GC.config(full_mark: true)`. How do you think?
> 
> Matz.

I really like the idea of having a set of config parameters to the GC. That seems like a much more robust way of providing custom features to the GC without having to make new methods every time.

So, based on the latest discussions, what I'd like to propose is this: 

* Introduce `GC.config`, currently with a single key `full_mark`.
  * `full_mark: true` should be the default behaviour - ie. the GC will work as it currently does.
* Ruby code can set `full_mark` to false like this: `GC.config(full_mark: false)` - In this case, no major GC's will be run unless explicitly requested using `GC.start(full_mark: true)`
* A user can check whether a major GC is needed at any time by checking `GC.latest_gc_info(:needs_major_by)`. Any value other than `nil` means that the GC would do a major on the next invocation.

I think this has a few benefits over the existing approach that I proposed in this ticket

1. Introduces a flexible way of providing config information to the GC. This will also allow pluggable GC's implemented in the future to define their own config keys without changing the interface of the `GC` module.
2. Does not introduce any new methods that are implementation specific to the current GC. This removes the decision about how future GC module needs to respond to these methods.


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-108763

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118489] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (18 preceding siblings ...)
  2024-06-10 11:23 ` [ruby-core:118265] " eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-07-08 17:47 ` ko1 (Koichi Sasada) via ruby-core
  2024-07-08 18:17 ` [ruby-core:118491] " byroot (Jean Boussier) via ruby-core
                   ` (3 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: ko1 (Koichi Sasada) via ruby-core @ 2024-07-08 17:47 UTC (permalink / raw)
  To: ruby-core; +Cc: ko1 (Koichi Sasada)

Issue #20443 has been updated by ko1 (Koichi Sasada).


> Introduce GC.config, currently with a single key full_mark.

I'm okay to introduce it but not sure `config` or `configure` or other word? (English issue).

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-109009

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118491] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (19 preceding siblings ...)
  2024-07-08 17:47 ` [ruby-core:118489] " ko1 (Koichi Sasada) via ruby-core
@ 2024-07-08 18:17 ` byroot (Jean Boussier) via ruby-core
  2024-07-08 19:22 ` [ruby-core:118495] " eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (2 subsequent siblings)
  23 siblings, 0 replies; 25+ messages in thread
From: byroot (Jean Boussier) via ruby-core @ 2024-07-08 18:17 UTC (permalink / raw)
  To: ruby-core; +Cc: byroot (Jean Boussier)

Issue #20443 has been updated by byroot (Jean Boussier).


>  but not sure config or configure or other word? (English issue).

Given `GC.config` returns the current configuration, I can't be `.configure`.

It could be `.configuration`, or any other concept. We can add it to the upcoming dev meeting to get a decision on that name.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-109011

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118495] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (20 preceding siblings ...)
  2024-07-08 18:17 ` [ruby-core:118491] " byroot (Jean Boussier) via ruby-core
@ 2024-07-08 19:22 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  2024-07-11 10:04 ` [ruby-core:118557] " ko1 (Koichi Sasada) via ruby-core
  2024-07-11 20:21 ` [ruby-core:118567] " eightbitraptor (Matthew Valentine-House) via ruby-core
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-07-08 19:22 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


ko1 (Koichi Sasada) wrote in #note-22:
> > Introduce GC.config, currently with a single key full_mark.
> 
> I'm okay to introduce it but not sure `config` or `configure` or other word? (English issue).

I prefer `config` for the following reasons

- `configure` is a verb and therefore implies taking an action. as @byroot says, this doesn't fit with returning the existing configuration when no arguments are passed.
- `config` is already a familiar term to developers (config files, `config` object in Rails etc)
- `configuration` is just the un-abbreviated form of `config`. It means the same thing but it takes longer to type.

----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-109018

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118557] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (21 preceding siblings ...)
  2024-07-08 19:22 ` [ruby-core:118495] " eightbitraptor (Matthew Valentine-House) via ruby-core
@ 2024-07-11 10:04 ` ko1 (Koichi Sasada) via ruby-core
  2024-07-11 20:21 ` [ruby-core:118567] " eightbitraptor (Matthew Valentine-House) via ruby-core
  23 siblings, 0 replies; 25+ messages in thread
From: ko1 (Koichi Sasada) via ruby-core @ 2024-07-11 10:04 UTC (permalink / raw)
  To: ruby-core; +Cc: ko1 (Koichi Sasada)

Issue #20443 has been updated by ko1 (Koichi Sasada).


On dev-meeting, there is no objection on `GC.config()`.
Could you write the more doc of it? For example, returning value is unclear.

About keys:

* Matz prefer to add prefix for the keys, like `GC.config(gengc_full_mark: true)`.
* I think `GC.config(gengc_full_mark: true)` is not clear always do full marking or not (`gengc_full_mark: false` is clear that full marking is prohibited). `gengc_allow_full_mark: true/false`? too much?


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-109080

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [ruby-core:118567] [Ruby master Feature#20443] Allow Major GC's to be disabled
  2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
                   ` (22 preceding siblings ...)
  2024-07-11 10:04 ` [ruby-core:118557] " ko1 (Koichi Sasada) via ruby-core
@ 2024-07-11 20:21 ` eightbitraptor (Matthew Valentine-House) via ruby-core
  23 siblings, 0 replies; 25+ messages in thread
From: eightbitraptor (Matthew Valentine-House) via ruby-core @ 2024-07-11 20:21 UTC (permalink / raw)
  To: ruby-core; +Cc: eightbitraptor (Matthew Valentine-House)

Issue #20443 has been updated by eightbitraptor (Matthew Valentine-House).


ko1 (Koichi Sasada) wrote in #note-25:
> On dev-meeting, there is no objection on `GC.config()`.
> Could you write the more doc of it? For example, returning value is unclear.
> 
> About keys:
> 
> * Matz prefer to add prefix for the keys, like `GC.config(gengc_full_mark: true)`.
> * I think `GC.config(gengc_full_mark: true)` is not clear always do full marking or not (`gengc_full_mark: false` is clear that full marking is prohibited). `gengc_allow_full_mark: true/false`? too much?

Thank you for discussing this. I will ship this with `GC.config` as the method name. I have changed the config key to `rgengc_allow_full_mark`, because despite being long, it is descriptive and unambiguous. I have also fully documented the semantics of `GC.config`, as well as the behaviour of this key.


----------------------------------------
Feature #20443: Allow Major GC's to be disabled
https://bugs.ruby-lang.org/issues/20443#change-109089

* Author: eightbitraptor (Matthew Valentine-House)
* Status: Open
----------------------------------------
[[Github PR #10598]](https://github.com/ruby/ruby/pull/10598)

## Background

Ruby's GC running during Rails requests can have negative impacts on currently
running requests, causing applications to have high tail-latency.

A technique to mitigate this high tail-latency is Out-of-band GC (OOBGC). This
is basically where the application is run with GC disabled, and then GC is
explicitly started after each request, or when no requests are in progress.

This can reduce the tail latency, but also introduces problems of its own. Long
GC pauses after each request reduce throughput. This is more pronounced on
threading servers like Puma because all the threads have to finish processing
user requests and be "paused" before OOBGC can be triggered.

This throughput decrease happens for a couple of reasons:

1. There are few heuristics available for users to determine when GC should run,
this means that in OOBGC scenarios, it's possible that major GC's are being run
more than necessary.  2. The lack of any GC during a request means that lots of
garbage objects have been created and not cleaned up, so the process is using
more memory than it should - requiring major GC's run as part of OOBGC to do
more work and therefore take more time.

This ticket attempts to address these issues by:

1. Provide `GC.disable_major` and its antonym `GC.enable_major` to disable and
enable only major GC 2. Provide `GC.needs_major?` as a basic heuristic allowing
users to tell when Ruby should run a Major GC.

These ideas were originally proposed by @ko1 and @byroot in [this rails
issue](https://github.com/rails/rails/issues/50449)

Disabling GC major's would still allow minor GC's to run during the request,
avoiding the ballooning memory usage caused by not running GC at all, and
reducing the time that a major takes when we do run it, because the nursery
objects have been cleaned up during the request already so there is less work
for a major GC to do.

This can be used in combination with `GC.needs_major?` to selectively run an
OOBGC only when necessary

## Implementation

This PR adds 3 new methods to the `GC` module

- `GC.disable_major` This prevents major GC's from running automatically. It
  does not restrict minors. When `objspace->rgengc.need_major_gc` is set and a
  GC is run, instead of running a major, new heap pages will be allocated and a
  minor run instead. `objspace->rgengc.need_major_gc` will remain set until a
  major is manually run. If a major is not manually run then the process will
  eventually run out of memory.
  
  When major GC's are disabled, object promotion is disabled. That is, no
  objects will increment their ages during a minor GC. This is to attempt to
  minimise heap growth during the period between major GC's, by restricting the
  number of old-gen objects that will remain unconsidered by the GC until the
  next major.
  
  When `GC.start` is run, then major GC's will be enabled, a GC triggered with
  the options passed to `GC.start`, and then `disable_major` will be set to the
  state it was in before `GC.start` was called.
  
- `GC.enable_major` This simply unsets the bit preventing major GC's. This will
  revert the GC to normal generational behaviour. Everything behaves as default
  again.

- `GC.needs_major?` This exposes the value of `objspace->rgengc.need_major_gc`
  to the user level API. This is already exposed in
  `GC.latest_gc_info[:need_major_by]` but I felt that a simpler interface would
  make this easier to use and result in more readable code. eg.
  
```
out_of_band do 
  GC.start if GC.needs_major?  
end 
```

Because object aging is disabled when majors are disabled it is recommended to
use this in conjunction with `Process.warmup`, which will prepare the heap by
running a major GC, compacting the heap, and promoting every remaining object to
old-gen. This ensures that minor GC's are running over the smallets possible set
of young objects when `GC.disable_major` is true.

## Benchmarks

We ran some tests in production on Shopify's core monolith over a weekend and
found that:

**Mean time spent in GC, as well as p99.9 and p99.99 GC times are all
improved.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 41 49"
src="https://github.com/ruby/ruby/assets/31869/6cff5b11-2e21-40c1-bb84-d994e0e1798d">

**p99 GC time is slightly higher.** 

<img width="1000" alt="Screenshot 2024-04-22 at 16 44 55"
src="https://github.com/ruby/ruby/assets/31869/dc645cbe-9495-46f0-8485-24e790c42f32">

We're running far fewer OOBGC major GC's now that we have `GC.needs_major?` than
we were before, and we believe that this is contributing to a slightly increased
number of minor GC's. raising the p99 slightly.

**App response times are all improved**

We see a ~2% reduction in average response times when compared againststandard GC 
(~7% p99, ~3% p99.9 and ~4% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 17" src="https://gist.github.com/assets/31869/70e81fa5-77b2-469a-8945-88bf8f8fefe9">

This drops slightly to an a ~1% reduction in average response times when compared
against our normal OOBGC approach  (~6% p99, ~2% p99.9 and ~3% p99.99).

<img width="1000" alt="Screenshot 2024-04-23 at 09 27 29" src="https://gist.github.com/assets/31869/cbaa3807-0cd1-4dba-a5e6-b9df91024d73">


EDIT: to correct a formula error in the original Average charts, numbers updated. 

---Files--------------------------------
Capture d’écran 2024-04-22 à 18.41.52.png (279 KB)


-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2024-07-11 20:22 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-04-22 16:02 [ruby-core:117644] [Ruby master Feature#20443] Allow Major GC's to be disabled eightbitraptor (Matthew Valentine-House) via ruby-core
2024-04-22 16:45 ` [ruby-core:117645] " byroot (Jean Boussier) via ruby-core
2024-04-23 22:43 ` [ruby-core:117664] " nateberkopec (Nate Berkopec) via ruby-core
2024-04-24 10:17 ` [ruby-core:117676] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-04-24 11:57 ` [ruby-core:117682] " byroot (Jean Boussier) via ruby-core
2024-04-24 14:56 ` [ruby-core:117687] " shan (Shannon Skipper) via ruby-core
2024-04-25  0:59 ` [ruby-core:117695] " ko1 (Koichi Sasada) via ruby-core
2024-04-25  1:12 ` [ruby-core:117696] " ko1 (Koichi Sasada) via ruby-core
2024-04-25  3:44 ` [ruby-core:117699] " duerst via ruby-core
2024-04-25  6:15 ` [ruby-core:117700] " byroot (Jean Boussier) via ruby-core
2024-05-09 12:56 ` [ruby-core:117821] " matz (Yukihiro Matsumoto) via ruby-core
2024-05-09 20:29 ` [ruby-core:117824] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-05-12  6:49 ` [ruby-core:117843] " byroot (Jean Boussier) via ruby-core
2024-05-14  7:23 ` [ruby-core:117871] " matz (Yukihiro Matsumoto) via ruby-core
2024-06-03 18:26 ` [ruby-core:118156] " ko1 (Koichi Sasada) via ruby-core
2024-06-03 18:40 ` [ruby-core:118157] " ko1 (Koichi Sasada) via ruby-core
2024-06-03 21:24 ` [ruby-core:118161] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-06-04  8:21 ` [ruby-core:118167] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-06-06  8:40 ` [ruby-core:118198] " matz (Yukihiro Matsumoto) via ruby-core
2024-06-10 11:23 ` [ruby-core:118265] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-07-08 17:47 ` [ruby-core:118489] " ko1 (Koichi Sasada) via ruby-core
2024-07-08 18:17 ` [ruby-core:118491] " byroot (Jean Boussier) via ruby-core
2024-07-08 19:22 ` [ruby-core:118495] " eightbitraptor (Matthew Valentine-House) via ruby-core
2024-07-11 10:04 ` [ruby-core:118557] " ko1 (Koichi Sasada) via ruby-core
2024-07-11 20:21 ` [ruby-core:118567] " eightbitraptor (Matthew Valentine-House) via ruby-core

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).