galaaz 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -4
- data/README.md +79 -32
- data/bin/galaaz +10 -2
- data/bin/galaaz-jruby +0 -0
- data/bin/galaaz-ruby +0 -0
- data/bin/galaaz_jruby_env.inc.sh +19 -3
- data/bin/galaaz_ruby_env.inc.sh +2 -0
- data/bin/gbookdown +0 -0
- data/blogs/R-on-Rails-Planning-Document.md +79 -112
- data/blogs/manual/manual.md +79 -32
- data/lib/R_interface/r_arrow.rb +37 -0
- data/lib/galaaz/arrow_ipc/java_arrow_backend.rb +250 -0
- data/lib/galaaz/arrow_ipc/red_arrow_backend.rb +126 -0
- data/lib/galaaz/arrow_ipc.rb +167 -0
- data/lib/galaaz/cli.rb +531 -0
- data/lib/galaaz.rb +6 -0
- data/lib/galaaz_jruby.rb +17 -2
- data/new_bridge_specs/arrow_ipc_async_spec.rb +90 -0
- data/new_bridge_specs/arrow_ipc_export_async_spec.rb +68 -0
- data/r_requires/arrow.txt +3 -0
- data/r_requires/knit-extras.txt +4 -0
- data/r_requires/knit.txt +4 -0
- data/specs/arrow_ipc_export_spec.rb +62 -0
- data/specs/arrow_ipc_handoff_spec.rb +97 -0
- data/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 671c2fe1fb15dbd12a7f491e52c318d01ebe9625e78d9acdaaa3dffbe3efbd3d
|
|
4
|
+
data.tar.gz: 2d4d63f915f0b8a09cff53df98802f7bcaff0298f9247ef38b583b2a0e902730
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1dc8f434731a05f495909d5f8bc70949f7ec7ae8034fbe8dd6a026f6b9592d426d573dffc0f13326ddfb7c830112bd6922fcdb8ca717e091d8722e219ea43de6
|
|
7
|
+
data.tar.gz: d594128365977ac52a3a2d15f6e3dd5f1b97ee312c0c6232883f6fb8838fb7c1440f2272c3302e53853026befd7e625f6e4124a8d3ddd32959f7bf665cb8cda7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.1
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- CLI on `bin/galaaz`: `setup`, `blogs init`, `doctor`, `add` (knit / arrow / tex / bio /
|
|
8
|
+
examples / ledger / demo). Legacy unknown args still forward to rake.
|
|
9
|
+
- Omarchy dogfood helpers under `script/omarchy/` (install/remove scripts + menu overlay).
|
|
10
|
+
- `galaaz doctor` reports both CRuby and JRuby when available (mise-aware).
|
|
11
|
+
- Package lists `r_requires/knit.txt`, `knit-extras.txt`, `arrow.txt` for add-on installs.
|
|
12
|
+
- Cold-install CRuby path uses `galaaz setup` + `galaaz blogs init`.
|
|
13
|
+
|
|
14
|
+
### Notes
|
|
15
|
+
|
|
16
|
+
- Omarchy / stranger install remains `gem install galaaz` then `galaaz setup` (no
|
|
17
|
+
`bundle install` for core use). See `Documentation/PLAN_OMARCHY_INTEGRATION.md`.
|
|
18
|
+
|
|
3
19
|
## 2.1.0
|
|
4
20
|
|
|
5
21
|
Galaaz **2.1** keeps the **galaaz2_0** integration line as the main development branch and marks
|
|
@@ -10,14 +26,16 @@ Galaaz **2.1** keeps the **galaaz2_0** integration line as the main development
|
|
|
10
26
|
- First-class **CRuby** NewBridge path alongside JRuby (engine-aware launchers, cold-install /
|
|
11
27
|
CI coverage for both).
|
|
12
28
|
- Docs: treat JRuby and CRuby as equal supported runtimes for the bridge.
|
|
13
|
-
- [Documentation/ROADMAP_ARROW_RUBY_R.md](Documentation/ROADMAP_ARROW_RUBY_R.md) —
|
|
14
|
-
|
|
29
|
+
- [Documentation/ROADMAP_ARROW_RUBY_R.md](Documentation/ROADMAP_ARROW_RUBY_R.md) — stages A (copy),
|
|
30
|
+
B1/B2 (IPC/mmap file; shipped), C (shared-memory bus; future).
|
|
31
|
+
- Stage **B1/B2** APIs: `Galaaz::ArrowIpc`, `R::Arrow.open_ipc`, `R::Arrow.write_ipc` (CRuby
|
|
32
|
+
red-arrow + system Arrow GLib; JRuby Arrow Java + `JAVA_OPTS` nio opens).
|
|
15
33
|
|
|
16
34
|
### Notes
|
|
17
35
|
|
|
18
36
|
- Git branch **`galaaz2_0`** remains the integration branch name; the gem version is **2.1.0**.
|
|
19
|
-
- Apache Arrow **zero-copy shared RAM** is still future work
|
|
20
|
-
|
|
37
|
+
- Apache Arrow **zero-copy shared RAM** (Stage C) is still future work. Stage A copies into R;
|
|
38
|
+
Stage B uses an IPC file and only the path crosses NewBridge. See the roadmap.
|
|
21
39
|
|
|
22
40
|
## 2.0.0
|
|
23
41
|
|
data/README.md
CHANGED
|
@@ -59,9 +59,11 @@ CRuby when you prefer MRI. R remains the same **GNU R** you use interactively—
|
|
|
59
59
|
compiled extensions and Bioconductor. Earlier GraalVM / TruffleRuby / FastR experiments
|
|
60
60
|
are no longer the focus.
|
|
61
61
|
|
|
62
|
-
The bridge handles **communication and typing** between the two worlds
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
The bridge handles **communication and typing** between the two worlds. Large tables can use
|
|
63
|
+
**Apache Arrow** in two shipped modes (described later): **Stage A** copies Ruby batches into an
|
|
64
|
+
R-side Arrow table (`R::Arrow.from_ruby_batches`); **Stage B** writes an Arrow IPC file and only
|
|
65
|
+
the **path** crosses NewBridge (`Galaaz::ArrowIpc` + `R::Arrow.open_ipc` / `write_ipc`). Shared-heap
|
|
66
|
+
zero-copy is **Stage C** and is not shipped.
|
|
65
67
|
|
|
66
68
|
## R-on-Rails: the one-person app for R scientists
|
|
67
69
|
|
|
@@ -340,7 +342,7 @@ The supported install is **`gem install` + compile the gatekeeper**. You do not
|
|
|
340
342
|
make -C "${gem_dir}/ext/new_bridge" all
|
|
341
343
|
```
|
|
342
344
|
|
|
343
|
-
5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on Java 9
|
|
345
|
+
5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on **JRuby** (Java 9+), the child JVM needs `--add-opens=java.base/java.nio=ALL-UNNAMED` via **`JAVA_OPTS`** (from a checkout, `bin/galaaz-jruby` and `mise.toml` set this; a leading `jruby -J... -S bundle exec` does **not** pass `-J` to rspec). On **CRuby**, install Apache Arrow GLib (`libarrow-glib-dev` from the [Apache Arrow APT](https://arrow.apache.org/install/) repo) and `gem install red-arrow` matching `pkg-config --modversion arrow-glib`. Do not install the unrelated Rubygems package named `arrow`.
|
|
344
346
|
|
|
345
347
|
For **gKnit**, **knitr**, **rmarkdown**, and LaTeX (PDF output), install the corresponding R packages, **Pandoc**, and a TeX distribution if you need PDF; the repository includes helpers such as **`bin/install-tinytex`** where appropriate.
|
|
346
348
|
|
|
@@ -442,13 +444,15 @@ A practical pattern is:
|
|
|
442
444
|
|
|
443
445
|
1. Use threads (or a connection pool) to read from **multiple databases or shards** in parallel.
|
|
444
446
|
2. Merge the rows in Ruby under a `Mutex` if you collect into one structure.
|
|
445
|
-
3. Hand the merged table to R **once
|
|
446
|
-
|
|
447
|
+
3. Hand the merged table to R **once**: **`R::Arrow.from_ruby_batches`** (Stage A: copy into R) or
|
|
448
|
+
**`Galaaz::ArrowIpc.write` / `write_batches`** then **`R::Arrow.open_ipc`** (Stage B: IPC file;
|
|
449
|
+
only the path crosses the bridge). Then run dplyr in R.
|
|
447
450
|
|
|
448
451
|
A runnable sketch lives in
|
|
449
452
|
`examples/multithread_shards_to_r/shards_to_r.rb` (simulated shard queries; swap in your DB
|
|
450
453
|
driver). For concurrency tests on the bridge itself, see `specs/bridge_concurrent_spec.rb` and
|
|
451
|
-
`specs/arrow_from_ruby_batches_spec.rb`.
|
|
454
|
+
`specs/arrow_from_ruby_batches_spec.rb`. Stage B IPC tests: `specs/arrow_ipc_handoff_spec.rb`,
|
|
455
|
+
`specs/arrow_ipc_export_spec.rb`.
|
|
452
456
|
|
|
453
457
|
## Long-running R calls and a completion block
|
|
454
458
|
|
|
@@ -4025,35 +4029,77 @@ ans = flights[:all, E.list(R[:arr_delay], R[:dep_delay])]
|
|
|
4025
4029
|
# Apache Arrow
|
|
4026
4030
|
|
|
4027
4031
|
[Apache Arrow](https://arrow.apache.org/) is a **columnar** in-memory format used heavily in R
|
|
4028
|
-
and Python for analytics.
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4032
|
+
and Python for analytics. GNU R still runs in a **separate process**. Ruby does **not** hold a
|
|
4033
|
+
shared Arrow C++ table with R. Stages:
|
|
4034
|
+
|
|
4035
|
+
1. **Stage A (copy over the bridge):** Ruby row hashes → **`R::Arrow.from_ruby_batches`** builds
|
|
4036
|
+
an Arrow `Table` **inside GNU R**. You get a **proxy**.
|
|
4037
|
+
2. **Stage B1 (Ruby → R IPC file):** **`Galaaz::ArrowIpc.write`** / **`write_batches`** writes an
|
|
4038
|
+
Arrow IPC file (prefer **`/dev/shm`**); **`R::Arrow.open_ipc(path)`** opens it in R. Only the
|
|
4039
|
+
**path** crosses NewBridge. This is **mmap/IPC file handoff**, not a shared heap.
|
|
4040
|
+
3. **Stage B2 (R → Ruby IPC file):** **`R::Arrow.write_ipc(obj)`** writes uncompressed IPC; Ruby
|
|
4041
|
+
reads with **`Galaaz::ArrowIpc.read`** (column hash) or **`read_batches`** (row hashes). Call
|
|
4042
|
+
**`Galaaz::ArrowIpc.release(path)`** when finished.
|
|
4043
|
+
4. **Stage C (not shipped):** named shared-memory bus. Do not claim 0 ms shared RAM until then.
|
|
4044
|
+
See **`Documentation/ROADMAP_ARROW_RUBY_R.md`**.
|
|
4045
|
+
|
|
4046
|
+
After ingest, use R’s **`arrow`** / **`dplyr`** on the proxy (`group_by`, `summarise`, `collect`)
|
|
4047
|
+
and unbox only KPIs you need in Ruby.
|
|
4048
|
+
|
|
4049
|
+
**Optional Ruby backends for Stage B**
|
|
4050
|
+
|
|
4051
|
+
* **CRuby:** Apache **red-arrow** — `gem install red-arrow` pinned to the same major as
|
|
4052
|
+
`pkg-config --modversion arrow-glib`, plus system **Arrow GLib** (`libarrow-glib-dev` from the
|
|
4053
|
+
[Apache Arrow APT](https://arrow.apache.org/install/) repo). Do **not** install the unrelated
|
|
4054
|
+
legacy Rubygems package named `arrow`. `bundle exec` still sees a user-installed `red-arrow`
|
|
4055
|
+
via Galaaz’s load-path helper.
|
|
4056
|
+
* **JRuby:** Apache Arrow **Java** JARs — **`GALAAZ_ARROW_JARS`**, `~/arrow_jars`, or
|
|
4057
|
+
`jar-dependencies`. Export **`JAVA_OPTS=--add-opens=java.base/java.nio=ALL-UNNAMED`** on the
|
|
4058
|
+
**child** JVM (`bin/galaaz-jruby`, `mise.toml`). `jruby -J... -S bundle exec rspec` does **not**
|
|
4059
|
+
pass `-J` to rspec.
|
|
4060
|
+
|
|
4061
|
+
**R packages:** **`arrow`** and **`dplyr`**. B2 writes IPC with **`compression: 'uncompressed'`**
|
|
4062
|
+
so JRuby Arrow Java can read without extra compression JARs.
|
|
4063
|
+
|
|
4064
|
+
**Tests:** `specs/arrow_from_ruby_batches_spec.rb` (A);
|
|
4065
|
+
`specs/arrow_ipc_handoff_spec.rb`, `specs/arrow_ipc_export_spec.rb` (B, sync);
|
|
4066
|
+
`new_bridge_specs/arrow_ipc_async_spec.rb`, `new_bridge_specs/arrow_ipc_export_async_spec.rb` (B, async).
|
|
4067
|
+
|
|
4068
|
+
## `R::Arrow` and `Galaaz::ArrowIpc`
|
|
4069
|
+
|
|
4070
|
+
* **`R::Arrow.from_ruby_batches`** — Stage A ingest.
|
|
4071
|
+
* **`R::Arrow.open_ipc(path)`** — Stage B1: IPC file → R Table proxy.
|
|
4072
|
+
* **`R::Arrow.write_ipc(obj, path = nil)`** — Stage B2: R Table/tibble → IPC path (scratch if omitted).
|
|
4073
|
+
* **`Galaaz::ArrowIpc.write` / `write_batches` / `read` / `read_batches` / `allocate_path` / `release` / `available?`**
|
|
4074
|
+
* **`R::Arrow.table_from(df)`** — wrap an R `data.frame` / tibble as an Arrow table.
|
|
4075
|
+
* **`R::Arrow.read_feather` / `write_feather`**, **`read_parquet`**, **`dataset(path)`** — file and
|
|
4076
|
+
dataset IO on paths visible to R.
|
|
4037
4077
|
|
|
4038
|
-
|
|
4039
|
-
**`bin/galaaz-jruby`** (or the same JVM flags as in **`docs/testing.md`**) so the Arrow JNI stack is
|
|
4040
|
-
available.
|
|
4078
|
+
## Example: Stage B round-trip (IPC file)
|
|
4041
4079
|
|
|
4042
|
-
|
|
4080
|
+
Requires `Galaaz::ArrowIpc.available?` (red-arrow or Arrow JARs) and R **`arrow`**. Not knitted
|
|
4081
|
+
below so a machine without the optional backend still builds this manual.
|
|
4043
4082
|
|
|
4044
|
-
|
|
4083
|
+
```ruby
|
|
4084
|
+
path = Galaaz::ArrowIpc.write(id: [1, 2, 3], grp: %w[a a b], value: [1.0, 2.0, 3.5])
|
|
4085
|
+
tbl = R::Arrow.open_ipc(path)
|
|
4086
|
+
Galaaz::ArrowIpc.release(path)
|
|
4045
4087
|
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4088
|
+
summed = R.dplyr___summarise(R.dplyr___group_by(tbl, :grp), total: E.sum(:value))
|
|
4089
|
+
out_path = R::Arrow.write_ipc(summed)
|
|
4090
|
+
rows = Galaaz::ArrowIpc.read_batches(out_path)
|
|
4091
|
+
Galaaz::ArrowIpc.release(out_path)
|
|
4092
|
+
# rows => [{:grp=>"a", :total=>3.0}, {:grp=>"b", :total=>3.5}] (illustrative)
|
|
4093
|
+
```
|
|
4049
4094
|
|
|
4050
4095
|
## Example: many Ruby rows → Arrow in R → grouped statistics
|
|
4051
4096
|
|
|
4052
4097
|
The repository test **`slow-specs/arrow_large_pipeline_spec.rb`** builds **200k rows** in parallel
|
|
4053
|
-
(eight threads × 25,000 rows), pushes them through **`R::Arrow.from_ruby_batches
|
|
4054
|
-
**dplyr** group summaries match a Ruby reference calculation. The same logic appears
|
|
4055
|
-
**smaller scale** so this manual can knit quickly; increase `thread_count` and
|
|
4056
|
-
when experimenting locally.
|
|
4098
|
+
(eight threads × 25,000 rows), pushes them through **`R::Arrow.from_ruby_batches`** (Stage A), then
|
|
4099
|
+
checks that **dplyr** group summaries match a Ruby reference calculation. The same logic appears
|
|
4100
|
+
below at a **smaller scale** so this manual can knit quickly; increase `thread_count` and
|
|
4101
|
+
`rows_per_thread` when experimenting locally. For the same ingest **without** copying every cell
|
|
4102
|
+
over NewBridge, use Stage B (`write_batches` + `open_ipc`) instead of `from_ruby_batches`.
|
|
4057
4103
|
|
|
4058
4104
|
|
|
4059
4105
|
``` ruby
|
|
@@ -4119,10 +4165,10 @@ end
|
|
|
4119
4165
|
```
|
|
4120
4166
|
|
|
4121
4167
|
**What to notice:** (1) Ruby only sees **`Hash`** rows and Ruby **`Thread`** objects; (2) a single
|
|
4122
|
-
**`from_ruby_batches`** call
|
|
4168
|
+
**`from_ruby_batches`** call **copies** those columns into an Arrow table in R; (3) **`dplyr___group_by`** /
|
|
4123
4169
|
**`dplyr___summarise`** / **`dplyr___collect`** mirror **`dplyr::group_by`** /
|
|
4124
4170
|
**`dplyr::summarise`** / **`dplyr::collect`** on an Arrow-backed table. For a lighter test, see
|
|
4125
|
-
**`specs/arrow_from_ruby_batches_spec.rb`**; for the full-size benchmark, run
|
|
4171
|
+
**`specs/arrow_from_ruby_batches_spec.rb`**; for the full-size Stage A benchmark, run
|
|
4126
4172
|
**`bin/run_slow_rspec slow-specs/arrow_large_pipeline_spec.rb`**.
|
|
4127
4173
|
|
|
4128
4174
|
# Bioconductor and DESeq2
|
|
@@ -4260,8 +4306,9 @@ Practical tips:
|
|
|
4260
4306
|
glue.
|
|
4261
4307
|
* **Reuse one process**: running many short scripts cold-starts Ruby, the JVM, and R each time;
|
|
4262
4308
|
a long-lived process or repeated calls in one run amortize setup (see benchmarks below).
|
|
4263
|
-
* **Batch data**: merge shards in Ruby, then
|
|
4264
|
-
|
|
4309
|
+
* **Batch data**: merge shards in Ruby, then **`R::Arrow.from_ruby_batches`** (Stage A) or
|
|
4310
|
+
**`Galaaz::ArrowIpc`** + **`R::Arrow.open_ipc`** (Stage B) instead of millions of tiny R calls.
|
|
4311
|
+
When Ruby needs a bulky result table back, **`R::Arrow.write_ipc`** + **`Galaaz::ArrowIpc.read_batches`**.
|
|
4265
4312
|
|
|
4266
4313
|
For measured discussion (including DESeq2-style workloads and warm comparisons), see
|
|
4267
4314
|
**`docs/performance.md`** and **`docs/deseq2_airway_benchmark.md`** in the Galaaz repository.
|
data/bin/galaaz
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# Galaaz CLI: setup / blogs / doctor / add.
|
|
5
|
+
# Unknown commands still forward to rake (legacy example runners).
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
root = File.expand_path('..', __dir__)
|
|
8
|
+
lib = File.join(root, 'lib')
|
|
9
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
10
|
+
|
|
11
|
+
require 'galaaz/cli'
|
|
12
|
+
|
|
13
|
+
exit Galaaz::CLI.run(ARGV)
|
data/bin/galaaz-jruby
CHANGED
|
File without changes
|
data/bin/galaaz-ruby
CHANGED
|
File without changes
|
data/bin/galaaz_jruby_env.inc.sh
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
# Galaaz required JVM flags for JRuby (Apache Arrow on Java 9+).
|
|
1
|
+
# Galaaz required JVM flags for JRuby (Apache Arrow memory on Java 9+).
|
|
2
2
|
# Source from bin/*.sh: source "$ROOT/bin/galaaz_jruby_env.inc.sh"
|
|
3
3
|
#
|
|
4
|
-
# Source of truth in Ruby/Rake: lib/galaaz_jruby.rb (GalaazJRuby::
|
|
4
|
+
# Source of truth in Ruby/Rake: lib/galaaz_jruby.rb (GalaazJRuby::JAVA_NIO_ADD_OPENS).
|
|
5
5
|
# Optional extra -J flags: export GALAAZ_JRUBY_OPTS="-J-Xmx4g" (space-separated).
|
|
6
|
-
|
|
6
|
+
#
|
|
7
|
+
# JAVA_OPTS is exported so *child* JRuby processes (bundle exec rspec, mise exec)
|
|
8
|
+
# get the same opens flag. -J args on the outer command are not inherited by
|
|
9
|
+
# bundle exec's second JVM.
|
|
10
|
+
GALAAZ_JAVA_NIO_ADD_OPENS='--add-opens=java.base/java.nio=ALL-UNNAMED'
|
|
11
|
+
GALAAZ_REQUIRED_JRUBY_J_ARGS="-J${GALAAZ_JAVA_NIO_ADD_OPENS}"
|
|
12
|
+
|
|
13
|
+
case " ${JAVA_OPTS:-} " in
|
|
14
|
+
*" ${GALAAZ_JAVA_NIO_ADD_OPENS} "*) ;;
|
|
15
|
+
*)
|
|
16
|
+
if [ -n "${JAVA_OPTS:-}" ]; then
|
|
17
|
+
export JAVA_OPTS="${GALAAZ_JAVA_NIO_ADD_OPENS} ${JAVA_OPTS}"
|
|
18
|
+
else
|
|
19
|
+
export JAVA_OPTS="${GALAAZ_JAVA_NIO_ADD_OPENS}"
|
|
20
|
+
fi
|
|
21
|
+
;;
|
|
22
|
+
esac
|
data/bin/galaaz_ruby_env.inc.sh
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#
|
|
8
8
|
# JRuby and CRuby are both first-class. Override with GALAAZ_RUBY=jruby, GALAAZ_RUBY=ruby,
|
|
9
9
|
# or a full path. Extra JRuby flags: GALAAZ_JRUBY_OPTS="-J-Xmx4g" (ignored on CRuby).
|
|
10
|
+
# CRuby red-arrow uses system Arrow GLib (Apache Arrow APT / libarrow-glib-dev).
|
|
10
11
|
|
|
11
12
|
GALAAZ_RUBY_BIN="${GALAAZ_RUBY:-ruby}"
|
|
12
13
|
|
|
@@ -29,6 +30,7 @@ if [[ "$_galaaz_is_jruby" -eq 1 ]]; then
|
|
|
29
30
|
# shellcheck source=galaaz_jruby_env.inc.sh
|
|
30
31
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/galaaz_jruby_env.inc.sh"
|
|
31
32
|
GALAAZ_RUBY_J_ARGS="$GALAAZ_REQUIRED_JRUBY_J_ARGS ${GALAAZ_JRUBY_OPTS:-}"
|
|
33
|
+
# JAVA_OPTS is exported by galaaz_jruby_env.inc.sh for bundle-exec child JVMs
|
|
32
34
|
else
|
|
33
35
|
GALAAZ_REQUIRED_JRUBY_J_ARGS=''
|
|
34
36
|
GALAAZ_RUBY_J_ARGS=''
|
data/bin/gbookdown
CHANGED
|
File without changes
|
|
@@ -68,9 +68,9 @@ Rails never went away—it matured. Several factors make Rails compelling again:
|
|
|
68
68
|
|
|
69
69
|
1. **Rails (Ruby)**: Handles web requests, authentication, authorization, database operations, background jobs, email, APIs, caching, sessions, and orchestration
|
|
70
70
|
2. **R**: Handles statistical modeling, machine learning, visualization, and data analysis
|
|
71
|
-
3. **Galaaz**: The seamless bridge that makes this integration feel like a single system—
|
|
71
|
+
3. **Galaaz**: The seamless bridge that makes this integration feel like a single system—proxies for analytics in R, Apache Arrow for bulky tables (Stage A copy, Stage B IPC/mmap file; shared-heap zero-copy is Stage C and not shipped)
|
|
72
72
|
|
|
73
|
-
**The Value Proposition**: Keep everything you love about R—the packages, the syntax, the statistical rigor—while gaining everything Rails provides for production applications.
|
|
73
|
+
**The Value Proposition**: Keep everything you love about R—the packages, the syntax, the statistical rigor—while gaining everything Rails provides for production applications. For large tables, prefer Arrow **IPC files** (path across the bridge, bytes on disk or `/dev/shm`) over CSV/JSON serialization. Do **not** claim Ruby and R share one physical Arrow heap until Stage C ships.
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -86,25 +86,24 @@ Earlier versions of Galaaz explored GraalVM's polyglot capabilities with FastR (
|
|
|
86
86
|
├─────────────────────────────────────────────────────────────────┤
|
|
87
87
|
│ │
|
|
88
88
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
|
|
89
|
-
│ │
|
|
90
|
-
│ │
|
|
91
|
-
│ └──────────────┘
|
|
92
|
-
│ │ │ │
|
|
89
|
+
│ │ JRuby/CRuby │ ←─────→ │ Galaaz Bridge│ ←─────→ │ GNU R │ │
|
|
90
|
+
│ │ │ path + │ (NewBridge) │ C/R │ Process │ │
|
|
91
|
+
│ └──────────────┘ cmds └──────────────┘ └──────────┘ │
|
|
93
92
|
│ │ │ │
|
|
94
93
|
│ ▼ ▼ │
|
|
95
94
|
│ ┌──────────────┐ ┌──────────┐│
|
|
96
95
|
│ │ Rails App │ │ Arrow ││
|
|
97
|
-
│ │
|
|
98
|
-
│ │
|
|
96
|
+
│ │ (threads / │ bulky tables: IPC file │ Table in ││
|
|
97
|
+
│ │ processes) │ ──── mmap / /dev/shm (Stage B) ──│ R (proxy)││
|
|
99
98
|
│ └──────────────┘ └──────────┘│
|
|
100
99
|
│ │
|
|
101
100
|
└─────────────────────────────────────────────────────────────────┘
|
|
102
101
|
```
|
|
103
102
|
|
|
104
103
|
**How it works:**
|
|
105
|
-
1. **JRuby** runs
|
|
106
|
-
2. **Galaaz Bridge** communicates with a GNU R process
|
|
107
|
-
3. **Apache Arrow** (optional)
|
|
104
|
+
1. **JRuby or CRuby** runs Rails and application code (JRuby for true JVM threads; CRuby for MRI). Both talk the same NewBridge protocol.
|
|
105
|
+
2. **Galaaz Bridge** communicates with a GNU R process (NewBridge / gatekeeper)—commands and small values, not bulk bytes when you use Stage B.
|
|
106
|
+
3. **Apache Arrow** (optional): **Stage A** copies Ruby batches into an R-side Arrow Table (`R::Arrow.from_ruby_batches`). **Stage B** writes an Arrow IPC file; only the **path** crosses the bridge (`Galaaz::ArrowIpc` + `R::Arrow.open_ipc` / `write_ipc`). **Stage C** (shared-heap zero-copy) is not shipped—see `Documentation/ROADMAP_ARROW_RUBY_R.md`.
|
|
108
107
|
4. **GNU R** is the actual R interpreter—full compatibility with all R packages (ggplot2, dplyr, Bioconductor, etc.)
|
|
109
108
|
|
|
110
109
|
**Why this is better than FastR/GraalVM:**
|
|
@@ -448,13 +447,22 @@ end
|
|
|
448
447
|
|
|
449
448
|
---
|
|
450
449
|
|
|
451
|
-
### Section 6: Apache Arrow
|
|
450
|
+
### Section 6: Apache Arrow — bulky tables without CSV/JSON
|
|
451
|
+
|
|
452
|
+
**Key Message**: Traditional bridges often serialize through text (CSV, JSON). Galaaz uses Apache Arrow in **two shipped modes**. Neither is a shared Ruby+R heap. Maintainer detail: `Documentation/ROADMAP_ARROW_RUBY_R.md`. User manual: `# Apache Arrow` in `README.md` / `blogs/manual/manual.md`.
|
|
452
453
|
|
|
453
|
-
|
|
454
|
+
| Stage | API | What actually happens |
|
|
455
|
+
|-------|-----|------------------------|
|
|
456
|
+
| **A** (shipped) | `R::Arrow.from_ruby_batches` / `table_from` | Columns are **copied** into GNU R; Ruby holds a **proxy**. dplyr then runs in R. |
|
|
457
|
+
| **B1** (shipped) | `Galaaz::ArrowIpc.write` → `R::Arrow.open_ipc(path)` | Ruby writes an Arrow **IPC file** (prefer `/dev/shm`); NewBridge carries only the **path**. R memory-maps / reads the file into an Arrow Table. |
|
|
458
|
+
| **B2** (shipped) | `R::Arrow.write_ipc` → `Galaaz::ArrowIpc.read` / `read_batches` | R writes IPC (uncompressed so JRuby Arrow Java can read); Ruby reads columns or row hashes for DB/API. |
|
|
459
|
+
| **C** (future) | not shipped | Named shared segment; do **not** claim 0 ms shared RAM until this exists and is measured. |
|
|
460
|
+
|
|
461
|
+
**Writers/readers (Stage B):** CRuby uses **red-arrow** (`gem install red-arrow` matching `pkg-config --modversion arrow-glib`, plus Apache Arrow APT / `libarrow-glib-dev`). JRuby uses **Apache Arrow Java** JARs (`GALAAZ_ARROW_JARS` or `~/arrow_jars`) and `JAVA_OPTS` `--add-opens=java.base/java.nio=ALL-UNNAMED` on the **child** JVM (`bin/galaaz-jruby` / `mise.toml`). Do not install the unrelated Rubygems gem named `arrow`.
|
|
454
462
|
|
|
455
463
|
**The Problem: Data Transfer Overhead**
|
|
456
464
|
|
|
457
|
-
In traditional polyglot systems (rpy2, reticulate), passing data between languages
|
|
465
|
+
In traditional polyglot systems (rpy2, reticulate), passing data between languages often means a text round-trip:
|
|
458
466
|
|
|
459
467
|
```python
|
|
460
468
|
# Python → R via JSON/CSV serialization (SLOW)
|
|
@@ -470,36 +478,31 @@ ro.globalenv['r_df'] = pandas2ri.py2rpy(df)
|
|
|
470
478
|
This approach:
|
|
471
479
|
- Copies all data through a text format
|
|
472
480
|
- Loses type information (factors become strings, dates become strings)
|
|
473
|
-
- Consumes
|
|
474
|
-
- Becomes a bottleneck at
|
|
481
|
+
- Consumes extra memory during transfer
|
|
482
|
+
- Becomes a bottleneck at large row counts
|
|
475
483
|
|
|
476
|
-
**
|
|
484
|
+
**Stage A: copy into R, then Remote Control**
|
|
477
485
|
|
|
478
|
-
|
|
486
|
+
Assemble row hashes in Ruby (threads OK on JRuby), then one ingest. Analytics stay on the R proxy; unbox KPIs only.
|
|
479
487
|
|
|
480
488
|
```ruby
|
|
481
489
|
require 'galaaz'
|
|
482
490
|
|
|
483
|
-
# 1. Build data in Ruby—using parallel threads for multi-source ingestion
|
|
484
491
|
batches = []
|
|
485
492
|
mutex = Mutex.new
|
|
486
493
|
threads = []
|
|
487
494
|
|
|
488
|
-
# Simulate parallel data ingestion from multiple sources
|
|
489
495
|
[0, 1, 2, 3].each do |tid|
|
|
490
496
|
threads << Thread.new do
|
|
491
|
-
|
|
492
|
-
local_data = fetch_from_source(tid) # Returns array of hashes
|
|
497
|
+
local_data = fetch_from_source(tid) # array of hashes
|
|
493
498
|
mutex.synchronize { batches << local_data }
|
|
494
499
|
end
|
|
495
500
|
end
|
|
496
501
|
threads.each(&:join)
|
|
497
502
|
|
|
498
|
-
#
|
|
499
|
-
# This is the key: data moves to R without serialization!
|
|
503
|
+
# Copy into an Arrow Table *inside GNU R* (not shared heap with Ruby)
|
|
500
504
|
table = R::Arrow.from_ruby_batches(batches)
|
|
501
505
|
|
|
502
|
-
# 3. Use R's dplyr directly on the Arrow table
|
|
503
506
|
R.install_and_loads('dplyr', 'arrow')
|
|
504
507
|
|
|
505
508
|
grouped = R.dplyr___group_by(table, :region)
|
|
@@ -510,57 +513,59 @@ summarised = R.dplyr___summarise(
|
|
|
510
513
|
total: E.sum(:value)
|
|
511
514
|
)
|
|
512
515
|
results = R.dplyr___collect(summarised)
|
|
513
|
-
|
|
514
516
|
puts results
|
|
515
|
-
#> # A tibble: 4 × 4
|
|
516
|
-
#> region count avg_value total
|
|
517
|
-
#> <chr> <int> <dbl> <dbl>
|
|
518
|
-
#> 1 North 25000 5.5 137500
|
|
519
|
-
#> 2 South 25000 5.5 137500
|
|
520
|
-
#> 3 East 25000 5.5 137500
|
|
521
|
-
#> 4 West 25000 5.5 137500
|
|
522
517
|
```
|
|
523
518
|
|
|
524
|
-
**
|
|
519
|
+
**Stage B: IPC file; path on the bridge**
|
|
520
|
+
|
|
521
|
+
Use this when the table is large enough that copying every cell over NewBridge/MsgPack is the wrong tax. Scratch files prefer `/dev/shm`.
|
|
522
|
+
|
|
523
|
+
```ruby
|
|
524
|
+
require 'galaaz'
|
|
525
|
+
|
|
526
|
+
# B1: Ruby → file → R
|
|
527
|
+
path = Galaaz::ArrowIpc.write_batches(batches) # or .write(col => array, ...)
|
|
528
|
+
tbl = R::Arrow.open_ipc(path)
|
|
529
|
+
Galaaz::ArrowIpc.release(path) # unlink after R has opened (default B1 lifetime)
|
|
530
|
+
|
|
531
|
+
grouped = R.dplyr___group_by(tbl, :region)
|
|
532
|
+
summarised = R.dplyr___summarise(grouped, total: E.sum(:value))
|
|
533
|
+
|
|
534
|
+
# B2: R → file → Ruby (e.g. rows for ActiveRecord / JSON)
|
|
535
|
+
out_path = R::Arrow.write_ipc(summarised)
|
|
536
|
+
rows = Galaaz::ArrowIpc.read_batches(out_path)
|
|
537
|
+
Galaaz::ArrowIpc.release(out_path)
|
|
538
|
+
|
|
539
|
+
render json: { analytics: rows }
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Why this matters (honest):**
|
|
525
543
|
|
|
526
|
-
- **
|
|
527
|
-
- **Type
|
|
528
|
-
- **
|
|
529
|
-
- **
|
|
530
|
-
- **
|
|
544
|
+
- **Columnar Arrow**, not CSV, for the bulky payload
|
|
545
|
+
- **Type-preserving** numeric/string columns on the B1/B2 contract (int32/int64, float64, utf8; nulls kept)
|
|
546
|
+
- **Stage A** still copies; **Stage B** still materializes a file R (or Ruby) then reads—R may mmap the IPC file
|
|
547
|
+
- **Not** “Ruby and R map the same live buffer” until Stage C
|
|
548
|
+
- **Scale**: Stage A pipeline in `slow-specs/arrow_large_pipeline_spec.rb` (200K+ rows); Stage B specs in `specs/arrow_ipc_handoff_spec.rb`, `specs/arrow_ipc_export_spec.rb`, and the matching `new_bridge_specs/arrow_ipc_*_async_spec.rb`
|
|
549
|
+
- **Parquet/Feather/dataset** stay R-side file APIs (`R::Arrow.write_parquet`, `dataset`)
|
|
531
550
|
|
|
532
|
-
**Real-
|
|
551
|
+
**Real-world sketch: shards in Ruby, analytics in R**
|
|
533
552
|
|
|
534
553
|
```ruby
|
|
535
|
-
#
|
|
536
|
-
# then analyze in R with zero-copy transfer
|
|
554
|
+
# Parallel ingest in Ruby, then one Arrow handoff (A or B), then dplyr in R.
|
|
537
555
|
|
|
538
556
|
require 'galaaz'
|
|
539
557
|
require 'active_record'
|
|
540
558
|
|
|
541
|
-
# Connect to multiple database shards
|
|
542
|
-
SHARDS = ['shard_1', 'shard_2', 'shard_3', 'shard_4'].map do |shard_name|
|
|
543
|
-
ActiveRecord::Base.establish_connection(
|
|
544
|
-
adapter: 'postgresql',
|
|
545
|
-
host: "#{shard_name}.db.internal",
|
|
546
|
-
database: 'analytics'
|
|
547
|
-
)
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
# Parallel data collection from all shards
|
|
551
559
|
batches = []
|
|
552
560
|
mutex = Mutex.new
|
|
553
561
|
|
|
554
562
|
SHARDS.each do |shard|
|
|
555
563
|
Thread.new do
|
|
556
|
-
# Query this shard
|
|
557
564
|
records = shard.connection.select_all(<<-SQL).cast_values
|
|
558
565
|
SELECT user_id, region, event_type, value, created_at
|
|
559
566
|
FROM events
|
|
560
567
|
WHERE created_at > NOW() - INTERVAL '7 days'
|
|
561
568
|
SQL
|
|
562
|
-
|
|
563
|
-
# Convert to array of hashes for Arrow
|
|
564
569
|
batch = records.map do |row|
|
|
565
570
|
{
|
|
566
571
|
user_id: row[0],
|
|
@@ -570,60 +575,37 @@ SHARDS.each do |shard|
|
|
|
570
575
|
created_at: row[4]
|
|
571
576
|
}
|
|
572
577
|
end
|
|
573
|
-
|
|
574
578
|
mutex.synchronize { batches << batch }
|
|
575
579
|
end
|
|
576
580
|
end
|
|
577
581
|
|
|
578
|
-
|
|
579
|
-
table = R::Arrow.from_ruby_batches(batches)
|
|
582
|
+
R.install_and_loads('dplyr', 'arrow')
|
|
580
583
|
|
|
581
|
-
#
|
|
582
|
-
|
|
584
|
+
# Prefer B1 when the merged table is large:
|
|
585
|
+
path = Galaaz::ArrowIpc.write_batches(batches)
|
|
586
|
+
table = R::Arrow.open_ipc(path)
|
|
587
|
+
Galaaz::ArrowIpc.release(path)
|
|
583
588
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
.dplyr___group_by(:region, :event_type) \
|
|
588
|
-
.dplyr___summarise(
|
|
589
|
+
analysis = R.dplyr___collect(
|
|
590
|
+
R.dplyr___summarise(
|
|
591
|
+
R.dplyr___group_by(table, :region, :event_type),
|
|
589
592
|
count: E.n(),
|
|
590
|
-
total_value: E.sum(:value)
|
|
591
|
-
avg_value: E.mean(:value),
|
|
592
|
-
unique_users: E.n_distinct(:user_id)
|
|
593
|
+
total_value: E.sum(:value)
|
|
593
594
|
)
|
|
595
|
+
)
|
|
594
596
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
results_hash = results.to_ruby
|
|
600
|
-
render json: { analytics: results_hash }
|
|
597
|
+
out_path = R::Arrow.write_ipc(analysis)
|
|
598
|
+
rows = Galaaz::ArrowIpc.read_batches(out_path)
|
|
599
|
+
Galaaz::ArrowIpc.release(out_path)
|
|
600
|
+
render json: { analytics: rows }
|
|
601
601
|
```
|
|
602
602
|
|
|
603
|
-
**Parquet and Feather
|
|
603
|
+
**Parquet and Feather (R-visible paths):**
|
|
604
604
|
|
|
605
605
|
```ruby
|
|
606
|
-
# Save Ruby data as Parquet (columnar, compressed)
|
|
607
|
-
# Then read directly into R without parsing
|
|
608
|
-
|
|
609
|
-
# 1. Build large dataset in Ruby
|
|
610
|
-
data = (1..1_000_000).map do |i|
|
|
611
|
-
{
|
|
612
|
-
id: i,
|
|
613
|
-
category: ["A", "B", "C", "D"][i % 4],
|
|
614
|
-
value: rand * 100,
|
|
615
|
-
timestamp: Time.now - (i % 86400)
|
|
616
|
-
}
|
|
617
|
-
end
|
|
618
|
-
|
|
619
|
-
# 2. Convert to R data.frame, then write as Parquet
|
|
620
606
|
df = R.data__frame(data)
|
|
621
607
|
R::Arrow.write_parquet(df, '/data/events.parquet')
|
|
622
|
-
|
|
623
|
-
# 3. Later, read directly into R as a dataset (lazy, memory-mapped)
|
|
624
608
|
dataset = R::Arrow.dataset('/data/events.parquet')
|
|
625
|
-
|
|
626
|
-
# Query without loading entire file
|
|
627
609
|
summary = R.dplyr___collect(
|
|
628
610
|
dataset \
|
|
629
611
|
.dplyr___filter(R[:value] > 50) \
|
|
@@ -632,28 +614,13 @@ summary = R.dplyr___collect(
|
|
|
632
614
|
)
|
|
633
615
|
```
|
|
634
616
|
|
|
635
|
-
**
|
|
636
|
-
|
|
637
|
-
```ruby
|
|
638
|
-
# Test: 8 threads × 25,000 rows = 200,000 rows
|
|
639
|
-
# With weighted aggregations in R
|
|
640
|
-
thread_count = 8
|
|
641
|
-
rows_per_thread = 25_000
|
|
642
|
-
group_count = 10
|
|
643
|
-
|
|
644
|
-
# Ruby parallel batch construction... 8 threads
|
|
645
|
-
# Arrow table creation from batches → instant
|
|
646
|
-
# R dplyr group_by + summarise → native speed
|
|
647
|
-
# Results verified accurate against Ruby reference implementation
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
**Key Insight**: With Arrow, the boundary between Ruby and R disappears for data. You can build data pipelines in Ruby (with its superior concurrency and database libraries) and analyze in R (with its statistical ecosystem)—with **zero overhead** at the language boundary.
|
|
617
|
+
**Key Insight**: Build pipelines in Ruby (concurrency, ActiveRecord); run statistics in R. Pay **one** bulky handoff (copy or IPC file), then **move commands** on proxies. That is the production story today—not a shared Arrow heap.
|
|
651
618
|
|
|
652
619
|
---
|
|
653
620
|
|
|
654
621
|
### Section 7: Parallelism and Process Orchestration
|
|
655
622
|
|
|
656
|
-
**Key Message**: R is single-threaded. Galaaz solves this by orchestrating multiple R processes from Ruby
|
|
623
|
+
**Key Message**: R is single-threaded. Galaaz solves this by orchestrating multiple R processes from Ruby—Arrow for bulky ingest/export, proxies for analytics per worker.
|
|
657
624
|
|
|
658
625
|
**The Architecture**:
|
|
659
626
|
```
|
|
@@ -892,7 +859,7 @@ Galaaz 2.0 represents the maturation of the Ruby-R bridge:
|
|
|
892
859
|
- **JRuby + GNU R architecture**: Moved from experimental GraalVM/FastR to battle-tested JRuby and standard GNU R for full package compatibility and production stability
|
|
893
860
|
- **New bridge architecture**: More robust, faster, better error handling
|
|
894
861
|
- **Process management**: Built-in support for R process pools
|
|
895
|
-
- **Apache Arrow
|
|
862
|
+
- **Apache Arrow**: Stage A copy (`from_ruby_batches`) and Stage B IPC/mmap (`Galaaz::ArrowIpc`, `open_ipc` / `write_ipc`). Shared-heap zero-copy is Stage C. Build pipelines in Ruby, analyze in R, unbox KPIs.
|
|
896
863
|
- **gKnit improvements**: Better R Markdown integration
|
|
897
864
|
- **Rails integration**: First-class support for Rails patterns
|
|
898
865
|
|
|
@@ -907,7 +874,7 @@ For the full blog post, include references to:
|
|
|
907
874
|
- **NSE/dplyr**: `blogs/nse_dplyr/nse_dplyr.md`
|
|
908
875
|
- **Object-Oriented**: `blogs/oh_my/oh_my.md`
|
|
909
876
|
- **Plotting Tutorial**: `blogs/ruby_plot/ruby_plot.md`
|
|
910
|
-
- **Apache Arrow**: `specs/
|
|
877
|
+
- **Apache Arrow**: `Documentation/ROADMAP_ARROW_RUBY_R.md`; `specs/arrow_ipc_handoff_spec.rb`, `specs/arrow_ipc_export_spec.rb`, `new_bridge_specs/arrow_ipc_async_spec.rb`, `new_bridge_specs/arrow_ipc_export_async_spec.rb`; Stage A: `specs/arrow_from_ruby_batches_spec.rb`, `slow-specs/arrow_large_pipeline_spec.rb`
|
|
911
878
|
- **Specs**: `specs/r_nse.spec.rb`, `specs/r_vector_functions.spec.rb`
|
|
912
879
|
|
|
913
880
|
---
|