Apr 26, 2024 - 런타임 데드 코드 분석 도구 Scavenger

당신의 코드는 생각보다 많이 죽어있다.

라는 제목으로 Naver Deview 2023 발표가 있었습니다.

요약된 내용은 아래와 같습니다.

시스템이 나이를 먹어갈수록 API client들이 서비스를 중단하거나,
기획 스펙 아웃 등의 여러 사유로 인하여 더 이상 사용되지 않는 코드(이하 데드 코드)가 늘어갑니다.

내 API를 사용하던 client들이 사용 중단을 알려주면 좋겠지만,
대부분 사용을 하지 않게 되었다는 사실을 API 제공자에게 알려주지 않습니다.

이런 데드 코드를 주기적으로 확인하고,
청소해 주면 시스템을 쌩쌩하게 유지할 수 있겠지만,
혹시나 이 API를 호출하는 클라이언트가 남아 있을지 모른다는 공포감이 데드 코드를 방치하게 하고,
방치된 데드 코드는 암덩어리처럼 자라게 됩니다.

이 암덩어리는 컴파일과 부팅 속도를 지연시키며,
테스트를 느리게 하고,
리팩토링을 힘들게 하는 등, 두고두고 개발자를 괴롭힙니다.

Scavenger는 이런 문제를 해결하고자 만들어진 DevOps 플랫폼이고
이미 70여 개의 크고 작은 네이버 서비스에 적용되어 있습니다.
네이버가 오픈소스로 공개하고 있는 Pinpoint와 유사한 방식으로 시스템 부팅 시에 Java agent만 설정해 주면, 자동으로 어떤 메서드가 언제 마지막으로 호출이 되었고,
어떤 메서드가 더 이상 호출되지 않는 것인지 기록합니다.
그리고 이를 UI를 통해 쉽게 확인할 수 있습니다.

이 발표에서는 이번 DEVIEW를 통해 오픈소스로 공개하는 Scavenger라는 런타임 데드 코드 분석 도구를 소개하고,
네이버 페이 등의 신규 시스템으로 재개발하는 중에 Scavenger를 활용하여 어떻게 몇십만 라인의 데드 코드를 제거했는지 설명합니다.
또한 Scavenger을 구현하는 데 사용한 bytecode instrumentation 등의 기술적 백그라운드에 대해서도 살펴봅니다.

Scavenger를 사용하면 여러분의 Java 코드가 날씬해집니다!

위의 내용에 대한 동영상은 아래와 같습니다.

요약 내용 : https://news.hada.io/topic?id=8610

  • 네이버가 공개한 오픈소스. 네이버내 80여개 서비스에서 사용중
  • 데드코드 : 실행되지 않는 코드 / 실행되더라도 어플리케이션 동작에 영향을 미치지 않는 코드
  • 데드코드의 문제점
    • 시스템을 이해/유지보수 하기 어렵게 만듦
    • 성능/보안에 악영향을 줌
    • 컴파일/테스트 속도를 지연시켜 전체 개발 속도를 저하
  • Scavenger는
    • 디버깅 또는 로그를 추가하지 않고 메서드 호출이 확인 가능
    • 메서드 호출 기록을 수집하여 이를 시각화해 유저에게 보여줌
    • Java agent 방식으로 손쉽게 사용 가능
  • JVM 기반 언어(Java, Kotlin)만 지원
    • Python은 현재 베타이고, 그외 다양한 언어 지원 예정

pdf 문서 링크 : https://deview.kr/data/deview/session/attach/%5B225%5D%EB%9F%B0%ED%83%80%EC%9E%84+%EB%8D%B0%EB%93%9C%EC%BD%94%EB%93%9C+%EB%B6%84%EC%84%9D+Scavenger+-+%EB%8B%B9%EC%8B%A0%EC%9D%98+%EC%BD%94%EB%93%9C%EB%8A%94+%EC%83%9D%EA%B0%81%EB%B3%B4%EB%8B%A4+%EB%A7%8E%EC%9D%B4+%EC%A3%BD%EC%96%B4%EC%9E%88%EB%8B%A4..pdf
github opensource 위치 : https://github.com/naver/scavenger

관련 블로그 등을 검색해봤을 때,
package 또는 class 단위에서 어느 정도의 method가 호출되었는지 확인할 수 있으며,
controller 에 사용하지 않는 API 파악에 매우 도움이 될 것이라고 판단합니다.

Apr 21, 2024 - CloudRun 도입 전 Log 설정

대부분의 프로젝트에서 구글 클라우드의 로깅을 위한 Logback 통합 라이브러리가 build.gradle 에 설정되어있습니다.

implementation 'com.google.cloud:google-cloud-logging-logback:0.129.1-alpha'

이번에 CloudRun 도입시 ‘Failed to find a usable hardware address from the network interfaces; using random bytes:’ 오류가 발생했고, 문제가 생긴 이유는 Netty 로 인한 오류였습니다.

추가적으로 https://mvnrepository.com/ 사이트에서 확인 시, 종속성으로 인한 취약점 또한 가지고 있습니다. (https://mvnrepository.com/artifact/com.google.cloud/google-cloud-logging-logback) 그로인해, 새로운 logback 라이브러리를 도입하는 것이 좋을 듯 하며, 추천하는 라이브러리는 다음과 같습니다.

Logback

implementation 'ch.qos.logback:logback-classic:1.5.5'

https://mvnrepository.com/artifact/ch.qos.logback/logback-classic

log4j

implementation 'org.apache.logging.log4j:log4j-api:2.23.1'

https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api

둘 중에 하나를 선택하면 됩니다.
log4j 의 경우 2021년 경 보안 이슈가 있었는데, 지금은 버전이 올라가 적용을 하여도 문제 없습니다. https://namu.wiki/w/Log4j%20%EB%B3%B4%EC%95%88%20%EC%B7%A8%EC%95%BD%EC%A0%90%20%EC%82%AC%ED%83%9C?from=Log4j#s-2.1 https://www.igloo.co.kr/security-information/apache-log4j-%EC%B7%A8%EC%95%BD%EC%A0%90-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EB%8C%80%EC%9D%91%EB%B0%A9%EC%95%88/

현재 logback 라이브러가 종속성으로 인한 취약점이 있다고 maver repogitory사이트에서 확인되어, log4j 를 설정하는게 오히려 좋지 않을까 판단합니다.

logback 과 log4j 둘다 적용하여, 정상적으로 GCP의 로그탐색기에 수집되어 동작하는 것을 확인했습니다.

로그 관련 라이브러리를 다 제거하고, 순수한 스프링부트의 내장 라이브러리를 사용해도 무방하며(logback 과 동일한 구조로 실행됩니다.), 그래도 구현체를 주입시키는 게 좋다고 생각하여, logback 이나 log4j 로 변환하는 것에 대해 의견을 정해 진행하길 원합니다.

logback.xml

GCP 에 CloudRun 이 도입되게 된다면 (모든 데이터는 로그탐색기에서 보게 되므로),
기존에 file로 저장하거나, console로 저장하거나 자체 커스텀 하던 logback-xxx.xml 을 console-appender.xml 만 사용하도록 정의하는게 좋습니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <include resource="config/logback/console-appender.xml" />
    <logger name="kr.co.xxx.api" level="DEBUG"/>
    <root level="INFO">
        <appender-ref ref="APTICONSOLE"/>
    </root>
</configuration>

대부분 프로젝트는 console-appender.xml 과 file-rolling.xml 가 구성되도록 작업되어있고,
file의 경우 APTIROLLING , console의 경우 APTICONSOLE로 호출하게 개발된 것으로 파악됩니다.

CloudRun 의 경우, 위의 xml 로 정의된 것처럼 console(APTICONSOLE) 로 설정하면 됩니다.

Google Cloud Logging은 Google Cloud Platform (GCP)의 로그 관리 및 분석 서비스로 표준 출력 (stdout)과 표준 오류 (stderr) 스트림을 자동으로 수집합니다.
별도의 구성없이 자동으로 활성화되기 때문에, 로그 데이터를 로그탐색기로 보내어, 사용자가 쉽게 로그를 조회하고 분석할 수 있게 합니다.
오히려 실수로 APTIROLLING 와 APTICONSOLE를 2개가 동작하게 설정하면, 자동으로 로그 데이터를 2배로 보내게 되어, 관련 트래픽 비용이 2배로 발생하게 될 수 있습니다.

참고 사항1

private static final Logger log = LoggerFactory.getLogger(클래스명.class);

으로 해도 되나, 편리하게

@Slf4j

클래스 상단에 @Slf4j 를 설정하여 처리하는 게 코드 간결성이 더 편하다고 생각합니다.
Slf4j는 로깅에 대한 추상화 계층을 제공하는 라이브러리로, 다양한 백엔드 로깅 프레임워크 (예: Log4j, Logback, Java Util Logging 등)와의 통합을 제공해줍니다.
@Slf4j는 컴파일 시점에 실제 로깅 프레임워크를 결정하므로, 코드의 유연성을 높여주기에, Slf4j를 사용하면 코드의 종속성을 최소화합니다.

참고 사항2

로그를 전부 로그탐색기에 보게 되면, logback.xml 을 지금 처럼 다양한 파일(logback-local.xml, logback-qa.xml)이 아닌,

    <springProfile name="dev, stage, master">
        <logger level="INFO" name="xxx" />
        <root level="INFO" >
            <appender-ref ref="xxx" />
        </root>
    </springProfile>

와 같은 하나의 파일에서 관리하는 것도 좋다고 생각합니다.

Mar 2, 2024 - 제로부터 시작하는 러스트 백엔드 프로그래밍 1

러스트를 알게 된 시점은 StackOverflow(stackoverflow.com)에서 설문조사 중
현재 사용하는 언어 중, 내년에도 사용하고 싶은언어는? 이라고 질문을,
스택오버플로우 가입자 중 65,000명 에게 물어봤을 때 86.1 퍼센트를 차지한 언어가 Rust 였기 때문입니다.

러스트는 안전하고, 빠르고, 병렬성에 초점을 둔 시스템 프로그래밍 언어이다.

  • Rust 공식문서

러스트는 다른 언어에 존재하는 고충을 해결하면서 더욱 적은 단점으로 확실하게 러스트가 다른언어보다 한 발 앞서해 해준다.

  • 스택오버플로우 Rust Top 컨트리뷰터 Jake Goulding

라는 유명한 이야기가 있을 정도로 Rust 는 매우 핫한 언어입니다. ‘제로부터 시작하는 러스트 백엔드 프로그래밍’ 라는 책의 옮긴이 김모세님도, 머리말에서 ‘안전하고’, ‘병렬적이며’,’실용적인’ 언어로 설계되었다고 적혀있다.

이 책에서는 러스트가 API 개발을 위한 생상적인 언어가 될 수 있는지에 대한 의문을, 할 수 있다는 설명을 하면서 어떤 생산적인 라이브러리를 사용하면되는지 알려줍니다.

이 책은 툴링부터 시작하여, 도커를 거쳐 테스트 방법까지 고민하는 책입니다. 기초 문법을 배울려면 Rust in action 을 보는 게 조금 더 좋을 거 같고, (러스트 프로그래밍 공식 가이드는 아직 못봤기 때문에 본 책을 추천합니다.) 어느정도 배운 러스트를 실무에 사용하거나 실용적인 부분에 대해 학습할때 매우 좋은 책이라고 생각합니다.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

자세한 설명법이나 doc 에 대해서는 첨부를 통해 사이트를 알려줘, 사이트의 가이드를 통해 설치를 진행했습니다.

bymin@homeui-MacBookPro rust % curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

/Users/bymin/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory is located at:

/Users/bymin/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

/Users/bymin/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

/Users/bymin/.profile
/Users/bymin/.zshenv

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


default host triple: x86_64-apple-darwin
default toolchain: stable (default)
profile: default
modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
> 3

로 설치 후 ,

bymin@homeui-MacBookPro ~ % rustc --version
rustc 1.76.0 (07dca489a 2024-02-04)

2월 4일 버전으로 설치가 되는 것을 확인할 수 있었습니다.

IntelliJ rust 책에서 알려준 가이드로 접속하면 RustLover 라는 사이트로 이동이 됩니다. 젯브레인을 자주 사용하므로, RustLover(RR) 을 설치합니다.

이 책에서는 웹 프레임워크를 actix-web 을 사용하고, 주석을 통해서 모든 소스를 받을 수 있습니다.
https://github.com/LukeMathWalker/zero-to-production 로 pork 받아, 소스를 보면서 진행을 따라가는 것도 좋을 듯했습니다.

https://actix.rs 에 가서 기본 샘플을 그대로 따라하고, doc 을 한번 읽는 것이 좋을듯합니다.

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest};

#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}

async fn manual_hello() -> impl Responder {
HttpResponse::Ok().body("Hey there!")
}

async fn greet(req: HttpRequest) -> impl Responder {
let name = req.match_info().get("name").unwrap_or("World");
format!("hello {}!", &name)
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().route("/", web::get().to(greet))
.route("/{name}", web::get().to(greet))
})
.bind("127.0.0.1:8000")?
.run().await
}

위의 소스는 예제 소스와, 책의 소스를 혼합하여 서버를 구동하였습니다.

문서 상 Cargo.toml 의 상세내용을 추가해야합니다.

[package]
name = "zero2prod"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "4"
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }

책 내용과는 조금 다르게 변경되었지만, 정상적으로 동작됩니다.