Ajouter un warning sur la notion de row group size et sur le paramètre min_rows_per_group de write_dataset
Closed this issue · 4 comments
Le problème
arrow::write_dataset
ne fixe pas de limite basse au row group size, ce qui peut générer une dégradation très significative des performances en terme de temps de calcul et catastrophique en terme d'utilisation mémoire.
Les résultats d'une requête identique sur fideli_individu21.parquet avec :
- fideli_insee : d'origine fourni par l'INSEE
- fideli_groupby : partitionné par
dplyr::group_by
- fideli_partitioning : partitionné avec l'argument partitioning de
arrow::write_dataset
- fideli_100000 : partionné avec
dplyr::group_by
en fixant l'argument min_rows_per_group dearrow::write_dataset
à 100 000
fname duration error max_mem
<chr> <dbl> <chr> <dbl>
1 "fideli(fideli_insee)" 2.58 NA 747032
2 "fideli(\"fideli_groupby\")" 26.7 NA 11651424
3 "fideli(\"fideli_partitioning\")" 27.1 NA 11604048
4 "fideli(\"fideli_100000\")" 1.50 NA 557280
(voir le code ci-dessous pour reproduire)
Le code pour refaire le test
Le code pour générer les datasets :
library(arrow)
library(dplyr)
fideli_insee <- "fideli_individu21.parquet"
open_dataset(fideli_insee) |>
write_dataset("fideli_partitioning", partitioning = c("csdep"))
open_dataset(fideli_insee) |>
group_by(csdep) |>
write_dataset("fideli_groupby")
open_dataset(fideli_insee) |>
group_by(csdep) |>
write_dataset("fideli_100000", min_rows_per_group = 100000L)
Le script pour refaire les tests (installez https://github.com/nbc/timemoir et redémarrez votre session) :
library(timemoir)
library(arrow)
library(dplyr)
fideli_insee <- "fideli_individu21.parquet"
fideli <- function(file) {
personnes_mobiles <- open_dataset(file) %>%
filter(depcom != depcom_n_1 & anais_co < 2004) %>%
select(poids, anais_co) %>%
collect()
}
bind_rows(
timemoir(fideli(fideli_insee)),
timemoir(fideli("fideli_groupby")),
timemoir(fideli("fideli_partitioning")),
timemoir(fideli("fideli_100000"))
)
arrow ne donnant pas (en tout cas je n'ai pas trouvé) de méthode pour accéder au row group size, je colle une fonction utilisant duckdb en dessous et le résultat donne ça :
> parquet_metadata("fideli_individu21.parquet") |> summarize(mean = mean(row_group_num_rows))
# A tibble: 1 × 1
mean
<dbl>
1 32754.
> parquet_metadata("fideli_groupby") |> summarize(mean = mean(row_group_num_rows))
# A tibble: 1 × 1
mean
<dbl>
1 439.
> parquet_metadata("fideli_partitioning") |> summarize(mean = mean(row_group_num_rows))
# A tibble: 1 × 1
mean
<dbl>
1 439.
> parquet_metadata("fideli_100000") |> summarize(mean = mean(row_group_num_rows))
# A tibble: 1 × 1
mean
<dbl>
1 64786.
parquet_metadata <- function(data) {
if (dir.exists(data)) {
parquet <- file.path(data, '**/*.parquet')
} else if (file.exists(data)) {
parquet <- data
} else {
stop("parquet doit être un fichier (parquet) ou un répertoire (dataset)")
}
tryCatch(
{
con <- DBI::dbConnect(duckdb::duckdb())
DBI::dbGetQuery(con, glue::glue("SELECT * EXCLUDE(key_value_metadata) FROM parquet_metadata('{parquet}')")) |>
tibble::as_tibble()
},
finally = {
DBI::dbDisconnect(con, shutdown = TRUE)
}
)
}
J'ai enfin trouvé une solution full Rarrow pour avoir une idée du group group size d'un fichier parquet (en l'occurrence la moyenne) :
> file_path <- "/nfs/partage-r-sas/projet-parquet/fideli/2021/local_for_loop/csdep=75/part-0.parquet"
> a <- ParquetFileReader$create(file_path)
> a$num_rows / a$num_row_groups
[1] 50355.39
> file_path <- "/nfs/partage-r-sas/projet-parquet/fideli/2021/local/csdep=75/part-0.parquet"
> a <- ParquetFileReader$create(file_path)
> a$num_rows / a$num_row_groups
[1] 1150.82
Le premier est bon, le second catastrophique.
introduit par d5f7809