R 패키지 유지보수하기

R Packages

CRAN에 올라간 패키지를 유지보수하는 에피소드를 소개합니다.

유충현 https://choonghyunryu.github.io (한화생명)
2021-11-24

R 패키지 단상

생명입니다. 그리고 생태계입니다.

“오늘도 어떤 새 생명이 탄생하고, 어떤 생명은 죽음을 맞이합니다. 또 어떤 것은 강한 생명력으로 번성하여 주변의 생명을 위협하기도 합니다.”

저는 R 패키지도 하나의 생명체라 생각합니다.

“오늘도 어떤 새 패키지가 등록되고, 어떤 패키지는 기준을 만족치 못해 퇴출됩니다. 어떤 패지키는 버전업되어 다시 올라오고, 또 어떤 패키지는 R 사용자의 인기가 높아서 오늘도 많은 수가 다운로드됩니다. ggplot2 패키지가 lattice 패키지를 도태시켰으며, R 사용자의 입소문에 어떤 패키지는 다운로드 횟수가 증가합니다. 오늘도 CRAN 리파지토리에는 보이지 않는 생명체들이 아우성에 뜨거운 하루를 보내고 있습니다.”

private 패키지를 만들어 사용하다가 공개용 패키지인 dlookr을 만들어서 CRAN에 등록한 지 3년이 넘었습니다. 그러나 아직도 패키지를 업데이트한 후 CRAN에 제출할 때마다 조마조마합니다. 여러 기능을 추가하다보니 패키지를 체크하여 무결성을 검증하는 과정에서 문제가 발생하는 일이 종종 있기 때문입니다.

이번에는 패키지를 만들어서 CRAN에 등록하고 유지보수하는 과정의 에피소드를 소개하려 합니다.

CRAN R 패키지 현황

https://cloud.r-project.org/web/packages/available_packages_by_name.html 페이지에는 설치 가능한 패키지들의 목록이 정리되어 있습니다.

시시때때로 새 패키지가 올라오고, 또 어떤 패키지는 퇴출되기에 시점마다 다르지만, 지금 이 글을 쓸 시점은 18423개의 패키지가 등록되어 있습니다.

require(tidyverse)

available.packages(repos = "https://cran.rstudio.com/") %>% 
  nrow()
[1] 18423

CRAN 패키지는 세계 각지에 미러링 사이트를 두어서 패키지 설치의 로드를 분산하고 있습니다.

그러나 당신이 RStudio를 사용한다면, 당신은 RStudio에서 제공하는 미러링 사이트인 https://cran.rstudio.com/로부터 패키지를 설치할 것입니다.

options("repos")
$repos
                       CRAN 
"https://cran.rstudio.com/" 
attr(,"RStudio")
[1] TRUE

우리나라에서 운용중인 미러링 사이트도 이제는 네개입니다. 예전에는 국내에 할당된 미러링 사이트의 쿼더가 3개였던 것으로 기억합니다만.

미러링 사이트는 원본 리파지토리와 싱크를 맞추는 시점에 따라 패키지의 개수가 다를 수도 있습니다. 그리고 소소한 이슈로 미미하게 차이가 발생하기도 합니다.

국내 미러링 서버의 패키지 개수를 비교해보겠습니다.

repos_url <- c("https://ftp.harukasan.org/CRAN/",
               "https://cran.yu.ac.kr/",
               "https://cran.seoul.go.kr/",
               "https://cran.biodisk.org/")

pkgs <- repos_url %>% 
  purrr::map_int(function(x) {
  available.packages(repos = x) %>% 
  nrow()
})

names(pkgs) <- repos_url

pkgs
https://ftp.harukasan.org/CRAN/          https://cran.yu.ac.kr/ 
                          18423                           18423 
      https://cran.seoul.go.kr/       https://cran.biodisk.org/ 
                          18420                           18423 

MASS 패키지를 아시나요?

MASS는 “Modern Applied Statistics with S”1라는 책의 데이터와 분석 코드를 패키지화한 것입니다.

citation("MASS")

To cite the MASS package in publications use:

  Venables, W. N. & Ripley, B. D. (2002) Modern Applied
  Statistics with S. Fourth Edition. Springer, New York. ISBN
  0-387-95457-0

A BibTeX entry for LaTeX users is

  @Book{,
    title = {Modern Applied Statistics with S},
    author = {W. N. Venables and B. D. Ripley},
    publisher = {Springer},
    edition = {Fourth},
    address = {New York},
    year = {2002},
    note = {ISBN 0-387-95457-0},
    url = {http://www.stats.ox.ac.uk/pub/MASS4},
  }

“Modern Applied Statistics with S”는 R(S-PLUS) 세계에서는 바이블과 같은 서적입니다. 현재는 Fourth Edition인데, 저는 이 책의 Third Edition을 2001년 아마존에서 구입해서 국내로 배송받은 기억이 있습니다. 그 당시의 아마존은 도서와 CD등의 미디어 판매 사이트 수준이었는데, 격세지감을 느낍니다.

“Modern Applied Statistics with S”의 공저자인 브라이언 리플리(B. D. Ripley)는 R재단의 R 코어팀 멤버입니다. 옥스퍼드 대학교의 통계학과 교수였습니다. 아주 유명한 분이죠. R(S-PLUS) 세계에서의 바이블로 꼽히는 “S Programming”도 MASS처럼 W. N. Venables와 공저했습니다. 그리고 RODBC 패키지의 개발자이자 유지보수 관리자입니다.

R Foundation의 R Core팀 멤버

Figure 1: R Foundation의 R Core팀 멤버

한통의 이메일

11월 17일 브라이언 리플리에게 한통의 메일을 받았습니다. 자동 메일일수도 있으나, 이 메일은 11월 30일까지 “당신의 패키지에 문제가 있으니, 11월 30 이전에 수정해라. 수정하지 않으면 CRAN에서 퇴출하겠다.”라는 의미입니다. 최후통첩인 셈입니다. 예전에 이런 메일을 처음 받고, 가볍게 생각했다가 dlookr이 CRAN에서 퇴출되었던 경험이 있습니다.

R 패키지를 CRAN에 등록하고 유지보수하는 입장에서는 브라이언 리플리의 메일은 달갑지 않습니다만, 이것도 누군가 해야할 롤이니 감내해야할 부분입니다.

브라이언 리플리의 메일

Figure 2: 브라이언 리플리의 메일

2001년도 RODBC2가 버전업되면서, 이전 버전에서 잘 작동하던 한글이 제대로 표현되지 않은 적이 있습니다. 의외로 MASS의 저자인 브라이언 리플리가 RODBC의 개발자이기에 문제의 해결을 요청하는 메일을 작성했다가 된통 야단을 맞은 적이 있습니다. 메일 박스를 찾아보았는데, 해당 메일은 없더군요. 느낌은 아직도 기억합니다. 시크하기로 유명한 분입니다.

메일 수령의 원인

dlookr은 데이터를 시각화할 때, R에서 제공하는 기본 폰트외의 추가폰트(https://choonghyunryu.github.io/posts/2021-01-27-rfonts/ 참고)를 사용합니다. 그런데 이 기능을 위해서 extrafont 패키지를 이용합니다.

extrafont 패키지는 TTF 폰트를 핸들링하는 ‘ttf2pt1’라는 유틸리티 프로그램을 연동하는 기능을 수행합니다만, ttf2pt1의 라이센스 정책상 별도의 패키지인 Rttf2pt1 패키지에 담아서 배포하고 있습니다. 즉, extrafont 패키지는 Rttf2pt1 패키지에 담아서 배포하는 ttf2pt1 유틸리티를 실행해야합니다.

그런데 ttf2pt1는 2003년도 이후부터 유지관리가 되지 않는 유틸리티입니다. 그래서 extrafont과 Rttf2pt1 패키지의 저자인 Winston Chang도 최신 컴파일러에서 Rttf2pt1를 컴파일하기 어려워지고 있어 CRAN에 유지관리가 어려우니 다른 대안을 찾으라고 권고하기도 했습니다.

최근 애플이 인텔의 CPU 대신 자사에서 개발한 ARM 기반의 CPU를 탑재한 노트북을 판매하면서, CRAN에서는 MacOS ARM64용 패키지를 배포합니다. 그런데 dlookr이 이 환경에서 패키지를 빌드할 때, ttf2pt1 유틸리티 호출때 에러가 발생한 것입니다.

ttf2pt1 유틸리티 호출때 발생하는 에러

Figure 3: ttf2pt1 유틸리티 호출때 발생하는 에러

2021년 9월에 dlookr 0.5.1을 등록할 때는 발생하지 않던 오류였는데, 정기적인 헬스 체크에 신규로 빌드하는 MacOS ARM64에서 오류가 발생하였던 것입니다.

패키지 체크

CRAN의 개별 패키지 페이지에는 ‘CRAN checks’라는 섹션이 있습니다. 이 섹션의 정보가 앞서 예시한 삽화입니다. 그리고 서머리 테이블만 발췌하면 다음과 같습니다. 이 화면은 기존 0.5.1 버전의 MacOS ARM64용 빌드 오류를 패치한 0.5.2버전이 등록되는 과정에서의 체크 정보입니다.

CRAN checks 서머리 테이블

Figure 4: CRAN checks 서머리 테이블

패키지 등록 요청이 받아지면, CRAN에서는 자동으로 수일에 거쳐 15개 환경에서의 패키지 바이너리가 빌드됩니다. 이 과정에서 패키지의 무결성을 검증하는 여러 체크항목이 수행되는데, 그 결과에 따라서 Status에 다음과 같은 상태값이 부여됩니다.

패키지 체크 항목

CRAN에 패키지를 등록하기 전에 패키지 개발자는 정합성 체크를 수행해야 합니다.

다음은 패키지를 빌드한 후, 정합성을 체크하는 명령어입니다. 쉘(명령행)에서 수행하는 명령어로 dlookr 0.5.2 버전을 예로 들었습니다.

R CMD build dlookr
R CMD check --as-cran dlookr_0.5.2.tar.gz

CRAN에서 패키지의 정합성을 체크하는 체크 룰을 갈수록 엄격해집니다. 이번에 패치버전을 체크할 때에는 새로운 체크 룰이 생겨서, 기존에는 정상으로 간주하던 것에서 경로가 발생하기도 했습니다.

다음은 많은 패키지들에서 발생하는 NOTE 사례입니다. CRAN은 R 패키지 배포본의 파일 크기를 5MB 이내로 제한합니다. 만약 배포본의 크기가 5MB보다 클 경우에 나타나는 NOTE 메시지입니다.

솔라리스용 바이너리의 NOTE 메시지

Figure 5: 솔라리스용 바이너리의 NOTE 메시지

사실 패키지 개발자는 15개 환경에서 체크하기에는 무리입니다. 해당 운영체제 환경을 모두 보유할 개발자는 아무도 없을 것입니다. 다행히 R-hub(https://builder.r-hub.io/)는 CRAN에서 배포하는 cross-patform 환경에서 패키지를 빌드하고 체크를 할 수 있는 서비스를 지원합니다. 그리고 rhub 패키지는 해당 서비스와 연동해서 여러 플랫폼에서 패키지를 빌드하고 체크할 수 있도록 도와줍니다.

그러나 저는 여러번 시도해보았지만 해당 서비스의 가상 host에서 dlookr을 빌드할 수 있는 환경을 제대로 만들어 주지 못했습니다. 그래서 저는 맥북프로의 MacOS에서 패키지를 개발하고, parallels로 가상의 MS-Windows와 docker의 가상 CentOS Linux에서 이상이 없는지 체크하고, 이상없을 경우에 CRAN에 제출합니다.

Vignettes 등의 도움말은 PDF 파일로도 생성되는데, 솔라리스 운영체제에서는 이 PDF 파일 크기가 다른 운영체제와는 달리 필요 이상으로 크게 만들어집니다. 저도 이런 이슈로 dlookr의 첨부 이미지 파일을 줄이거나 해상도를 낮춰 Vignettes 등의 도움말 파일의 용량을 줄였습니다만, 이번 0.5.2 버전에 나눔스퀘어 폰트를 포함하면서 솔라리스 운영체제에서 파일 용량이 5MB를 초과했나봅니다. 0.5.3 버전에서 용량을 줄여야겠습니다.

CRAN의 체크 항목은 패키지에서 참조(Depends 및 Import)하는 패키지의 개수도 20개 이내로 제한합니다. dlookr은 이미 20개의 타 패키지를 사용합니다. 그래서 추가하고 싶은 기능이 있으나, 패키지의 개수를 초과해서 보류하고 있습니다. 이것은 중요한 체크 항목입니다. 참조하는 패키지가 많은수록 패키지의 안정성은 떨어질 수 밖에 없습니다. 만약 어떤 패키지가 퇴출된다면, 그 패키지를 사용하는 다른 패키지도 퇴출되는 운영에 처합니다. 그래서 이 경우에는 Reverse imports 패키지의 유지 운영 담당자에게도 사전에 메일이 발송됩니다.

“당신의 B 패키지가 A라는 패키지를 사용하고 있는데, 이 패키지는 YYYY-MM-DD에 CRAN에서 퇴출되니, 그 전에 대안을 찾아 너의 패키지를 수정해라.” 이런 내용의 메일입니다. 저도 한번 받아보았는데, Reverse imports 패키지 개발자 중의 한 사람이 해당 패키지 개발자에게 문제제기를 하더라구요. 그리고 그 개발자가 패치를 만들어서 문제가 해결되었습니다.

그런데 문제가 완전히 해결되지 않았던 것입니다. 그 패키지가 extrafont였습니다. github의 해당 이슈에서 extrafont 개발자인 “Winston Chang”은 일단 불을 껐지만 안정적으로 유지보수할 수 없음을 이야기하면서 showtext 패키지를 추천했습니다. 그래서 0.5.2 버전에서는 extrafont 패키지를 제거하고 showtext 패키지를 추가했습니다. 그런데 아쉽게도 sysfonts 패키지까지 덤으로 추가해야 해서 20개 참조 패키지 수를 다 채워버렸습니다.

Reverse imports 패키지는 해당 패키지를 사용하는 다른 패키지들을 의미합니다. ggplot2나 dplyr 패키지의 Reverse imports 패키지의 수는 어마하게 많습니다. dlookr도 그 중 하나입니다. 이처럼 CRAN 패키지를 제출한 패키지 개발자는 사명감을 갖고 유지보수해야 합니다.

또한 통의 메일

올초 dlookr 패키지의 개발 시 어처구니없는 실수를 한 적이 있습니다. 시각화 매커니즘을 대대적으로 수정하면서, R의 기본 폰트가 아닌 외부 폰트를 사용하도록 로직을 수정했습니다.

R에는 몇개의 특수한 목적의 미리 정의된 이름의 함수가 있습니다. .onAttach() 함수는 패키지가 어태치되면서 자동으로 수행하라는 의미입니다. 쉽게 풀면, library(dlookr)을 수행하면 자동으로 로직이 실행되는 함수입니다. 이 함수에 사용자의 운용체제의 폰트 경로에서 특정 TTF 폰트를 R에 로드하는 로직을 넣었는데, 오타로 인해서 제가 테스트한 환경이 아닌 몇몇 플랫폼에서 에러가 발생한 것입니다. dlookr 특정 함수의 오류가 아니라, 아예 패키지가 로드되지 못하게 된 것입다.

역시나 브라이언 리플리에게 다음과 같은 메일이 왔습니다.

브라이언 리플리의 메일

Figure 6: 브라이언 리플리의 메일

그런데 그는 오타를 넘어 운영체제에서 폰트 경로를 찾는 다음 로직에 대해서도 문제를 제기했습니다.

get_ttf_paths <- function() {
  if (grepl("^darwin", R.version$os)) {
    paths <- c("/Library/Fonts/", "/System/Library/Fonts", 
               "~/Library/Fonts/")
    return(paths[file.exists(paths)])
  }
  else if (grepl("^linux-gnu", R.version$os)) {
    paths <- c("/usr/share/fonts/", "/usr/X11R6/lib/X11/fonts/TrueType/", 
               "~/.fonts/")
    return(paths[file.exists(paths)])
  }
  else if (grepl("^freebsd", R.version$os)) {
    paths <- c("/usr/local/share/fonts/truetype/", "/usr/local/lib/X11/fonts/", 
               "~/.fonts/")
    return(paths[file.exists(paths)])
  }
  else if (grepl("^mingw", R.version$os)) {
    return(paste(Sys.getenv("SystemRoot"), "\\Fonts", sep = ""))
  }
  else if (grepl("^SunOS", R.version$os)) {
    paths <- c("/usr/share/fonts/", "/usr/X11/lib/X11/fonts/TrueType/", 
               "~/.fonts/")
  }        
  else {
    #msg <- "Don't know where to look for truetype fonts."
    #packageStartupMessage(msg)
    return(character(0))
  }
}  

이 코드에서 darwin은 MacOS, mingw은 MS-WIndows, SunOS는 솔라리스 운영체제를 의미합니다. 그리고 Linux와 FreeBSD 까지 체크한, 나름 최선의 로직이라 생각했는데 그의 관점에서는 성에 차지 않았나봅니다.

아무튼 여전히 시크한 메일이지만, 그의 철학에 얼굴이 화끈거리는 창피함에 숙연해졌습니다.

시크하지만, 미워할 수 없는 브라이언 리플리입니다.

반가운 메일

브라이언 리플리의 메일은 대부분 경고성 메일인 반면, 우베 리게스(Uwe Ligges)의 메일은 긍정적인 측면의 메일입니다. 우베 리게스는 독일 도르트문트 대학교의 통계학 교수입니다. 이분도 R Core팀 멤버이지요.

R Foundation의 R Core팀 멤버

Figure 7: R Foundation의 R Core팀 멤버

우베 리게스의 메일도 다음처럼 대부분 자동 발송메일입니다.

우베 리게스의 메일

Figure 8: 우베 리게스의 메일

제출된 패키지가 CRAN의 체크를 패스해서, CRAN 리파지토리에 등록된다는 기분 좋은 메일입니다. 이 메일을 받으면, 이미 패키지 소스는 CRAN에 등록됩니다. 그리고 순차적으로 며칠동안 15개의 환경의 바이너리가 만들어지게 됩니다.

다음 메일은 MS-Windows에서 정상적으로 빌드되었다는 메시지입니다.

우베 리게스의 메일

Figure 9: 우베 리게스의 메일

그런데 우베 리게스의 메일은 수동 메일도 있습니다. 정상적으로 등록된 패키지가 내부 체크에서 오류가 발생하였을 때, 질문하면 친절하게 솔루션을 안내해 주기도 합니다. 그리고 지난 버전의 오류를 해결했는지 본인과 패키지 관리 담당자에게 설명하라고도 합니다.

감사의 인사

브라인언 리플리나 우베 리게스는 그 많은 CRAN의 패키지의 안정적인 운영을 위해서 기꺼이 자신의 시간을 할애했을 것입니다. 이들 뿐만 아니라, CRAN Core팀 및 전 세계의 R 패키지 개발자들은 이 풍성한 R 생태계를 위해서 지금도 R 스크립트를 만지고 있거나, 누군가의 메일에 답장을 쓰며, 누군가에게는 안정적인 패키지 운영을 위해 안내 메일을 작성하고 있겠죠. 그리고 프로그램화된 자동 메일은 누군가에서 기쁜 소식과 반갑지 않은 소식을 발송하고 있을 겁니다.

R 생태계의 발전을 위해 불철주야 활동하는 보이지 않는 그들에게 감사의 인사를 전합니다.


  1. 현재는 Fourth Edition인데 이전에는 Edition은 “Modern Applied Statistics with S-PLUS”라는 이름의 서적이었습니다.↩︎

  2. R에서 ODBC(Open Database Connectivity)통해 데이터베이스의 데이터를 핸들링할 수 있는 패키지↩︎

Citation

For attribution, please cite this work as

유충현 (2021, Nov. 24). Dataholic: R 패키지 유지보수하기. Retrieved from https://choonghyunryu.github.io/2021-11-24-rpackage

BibTeX citation

@misc{유충현2021r,
  author = {유충현, },
  title = {Dataholic: R 패키지 유지보수하기},
  url = {https://choonghyunryu.github.io/2021-11-24-rpackage},
  year = {2021}
}