포스트

2022-12-21-TIL

2022-12-21-TIL

Today I Learned

Go와 Java가 문자열과 메모리를 다루는 방식을 비교하고, 계층형 아키텍처, 권한 확인, 이미지 업로드, MDC 로깅 같은 Spring 실무 주제를 훑었다.


Go Memory Stack

Go에서는 변수를 스택에 둘지 힙에 둘지를 개발자가 정하지 않고 컴파일러가 정한다. Go FAQ에 따르면 주소가 취해진 변수는 힙 할당 후보가 되지만, 컴파일러의 이스케이프 분석(escape analysis)이 함수가 끝난 뒤에도 살아남지 않는다고 판단하면 스택에 둔다. 함수가 지역 변수의 포인터를 반환하는 경우처럼 값이 함수 밖으로 “탈출”하면 힙으로 간다. 힙 할당은 GC 부담으로 이어지므로, go build -gcflags=-m으로 어떤 값이 탈출하는지 확인하며 최적화하는 것이 링크된 글의 요지다.

  • https://medium.com/eureka-engineering/understanding-allocations-in-go-stack-heap-memory-9a2631b5035d

Java String

자바에서 String은 Object를 상속하는 객체 타입이다. 따라서 타입으로 저장하면 객체로 생성되고 그 객체에 대한 레퍼런스값이 저장될 것이다. 이 값은 64비트 메모리인 경우에는 64비트 주소값을 가지게 될 것이다.

한 가지 보충하면, JVM 명세는 reference 타입의 크기를 정하지 않고 구현에 맡긴다. HotSpot JVM은 64비트 환경에서도 기본으로 Compressed Oops가 켜져 있어서, java 명령 문서에 따르면 힙이 이 방식으로 표현 가능한 범위(기본 32GB) 안이면 객체 참조를 32비트로 표현한다. 즉 64비트 JVM이라도 참조 하나가 항상 8바이트를 차지하는 것은 아니다. 객체 크기를 정확히 재고 싶다면 링크의 DZone 글처럼 JOL 같은 도구로 측정하는 편이 낫다.

  • https://medium.com/omarelgabrys-blog/more-about-strings-in-java-fb2480af8a19
  • https://stackoverflow.com/questions/51466358/how-is-string-a-reference-type-in-java
  • https://dzone.com/articles/java-object-size-estimation-measuring-verifying
  • https://www.artima.com/insidejvm/ed2/jvm3.html
  • https://docs.oracle.com/javase/specs/jvms/se6/html/Overview.doc.html#7565
  • https://medium.com/@kavindaperera25/data-types-in-jvm-e45eedd00435

Java Equals and HashCode

자바에서 equals와 hashCode를 통해 동등 비교를 한다.

==는 두 참조가 같은 객체를 가리키는지(동일성)를, equals()는 논리적으로 같은 값인지(동등성)를 비교한다. Object.hashCode() 명세는 equals()로 같다고 판단되는 두 객체는 반드시 같은 hashCode를 반환해야 한다고 규정한다. 이 규칙을 어기면 HashMap, HashSet이 먼저 hashCode로 버킷을 찾기 때문에 같은 값의 객체를 찾지 못한다. 그래서 equals()를 재정의하면 hashCode()도 함께 재정의해야 한다.

  • https://mangkyu.tistory.com/101

Java String Constant Pool

JLS에 따르면 문자열 리터럴과 상수 표현식의 결과 문자열은 String.intern()을 호출한 것처럼 인턴되어 같은 인스턴스를 공유한다. 그래서 "abc" == "abc"는 true지만, new String("abc")는 힙에 새 객체를 만들므로 리터럴과 == 비교하면 false다. 문자열 비교에는 항상 equals()를 쓰는 이유다.

  • https://starkying.tistory.com/entry/what-is-java-string-pool

Strings in Golang

Go 블로그는 Go의 문자열을 사실상 읽기 전용 바이트 슬라이스로 설명한다. 문자열에는 임의의 바이트가 담길 수 있고 만든 뒤에는 바꿀 수 없다. 인코딩을 강제하지는 않지만 Go 소스 코드는 항상 UTF-8이므로, 바이트 이스케이프가 없는 리터럴은 유효한 UTF-8 바이트열이 된다. len(s)는 문자 수가 아니라 바이트 수이고, for range로 순회하면 rune(유니코드 코드 포인트) 단위로 읽는다.

  • https://perennialsky.medium.com/how-string-works-in-golang-7ac4d797164b
  • https://www.geeksforgeeks.org/strings-in-golang/

Layered Architecture

Controller, Service, Repository로 나누는 계층형 구조를 다시 봤다. 각 계층이 아래 계층에만 의존하도록 하면 관심사가 분리되지만, 계층을 지키는 것 자체가 목적이 되면 Service가 Repository 호출을 전달만 하는 얇은 계층이 되기 쉽다. Baeldung 글은 여기서 한 걸음 더 나아가 도메인과 유스케이스를 프레임워크로부터 분리하는 클린 아키텍처 방식으로 같은 예제를 재구성한다.

  • https://www.youtube.com/watch?v=QYDHxlpr04U
  • https://www.baeldung.com/spring-boot-clean-architecture
  • https://medium.com/swlh/designing-a-multi-layered-architecture-for-building-restful-web-services-with-spring-boot-and-a12ef85b77c9
  • https://www.javaguides.net/2020/07/spring-boot-project-architecture.html

Permission Check

웹에서 클라이언트가 권한에 따라 특정 요청을 하려면 어떻게 해야할까?

방법1) 토큰에 권한 정보를 담아서 요청하면, 요청에 맞는 응답을 내려준다.

방법2) 토큰에는 사용자 식별자만 담고, 서버가 요청마다 사용자의 권한을 조회해 판단한다. Atlassian의 Confluence 문서가 보여 주는 PermissionManager처럼 “이 사용자가 이 대상에 이 작업을 할 수 있는가”를 묻는 API를 서버 쪽에 두는 방식이다.

방법1은 조회 비용이 없지만 권한이 바뀌어도 토큰이 만료될 때까지 반영되지 않는다. 방법2는 항상 최신 권한을 쓰는 대신 매 요청 조회 비용이 들어 캐시를 함께 고민해야 한다. 어느 방법이든 클라이언트가 보낸 권한 정보를 그대로 믿지 말고 서버에서 서명 검증이나 조회로 확인해야 한다.

  • https://developer.atlassian.com/server/confluence/how-do-i-tell-if-a-user-has-permission-to/

Spring Blob Image Upload

이미지를 저장하는 방법은 DB의 BLOB 컬럼에 바이트로 넣는 방식과, 파일 시스템이나 Azure Blob Storage, S3 같은 오브젝트 스토리지에 두고 DB에는 경로만 저장하는 방식으로 나뉜다. BLOB은 트랜잭션과 백업을 DB와 함께 관리할 수 있지만 DB 크기와 부하가 커지고, 스토리지 방식은 CDN과 결합하기 쉽다.

컨트롤러에서 이미지를 내려줄 때는 byte[]나 Resource를 반환하고 produces = MediaType.IMAGE_JPEG_VALUE처럼 Content-Type을 지정하면 브라우저가 이미지로 렌더링한다.

  • https://medium.com/javarevisited/how-to-upload-file-to-azure-blob-using-spring-boot-8f0125b7f8af
  • https://www.appsloveworld.com/springmvc/100/76/when-saving-an-image-as-blob-how-to-display-it-in-jsp-with-text
  • https://www.baeldung.com/spring-controller-return-image-file
  • https://okky.kr/articles/568850
  • https://www.youtube.com/watch?v=TDa-if619FM
  • https://kkh0977.tistory.com/988
  • https://thisisspear.tistory.com/68
  • https://stackoverflow.com/questions/70389992/how-to-convert-blob-to-image-with-spring

Java Static Block vs Constructor

static 블록은 클래스가 초기화될 때 한 번 실행되고 static 필드를 준비하는 데 쓴다. 생성자는 new로 인스턴스를 만들 때마다 실행되어 인스턴스 필드를 초기화한다. 함께 링크한 글은 내부 클래스를 static으로 선언하지 않으면 바깥 인스턴스에 대한 숨은 참조를 갖게 된다는 이펙티브 자바의 조언을 정리한 것으로, static의 의미를 같이 짚어 볼 수 있다.

  • https://johngrib.github.io/wiki/java-inner-class-may-be-static/
  • https://study.com/academy/lesson/static-block-vs-constructor-in-java.html

Java HashMap Custom Key

직접 만든 클래스를 HashMap의 키로 쓰려면 앞에서 본 대로 equals()와 hashCode()를 함께 재정의해야 한다. 또 키로 넣은 뒤 hashCode 계산에 쓰이는 필드가 바뀌면 엔트리를 다시 찾을 수 없으므로 키 객체는 불변으로 만드는 것이 안전하다. Java 16 이상이라면 record가 두 메서드를 자동으로 만들어 준다.

  • http://www.gisdeveloper.co.kr/?p=5332

Java Logging with MDC

MDC(Mapped Diagnostic Context)는 SLF4J/Logback, Log4j 2가 제공하는 스레드별 키-값 저장소다. 요청이 들어올 때 필터에서 MDC.put("requestId", ...)로 값을 넣고 로그 패턴에 %X{requestId}를 넣으면, 그 요청을 처리하는 동안 찍히는 모든 로그에 같은 ID가 붙어 흐름을 추적하기 쉽다. 스레드 풀에서 스레드가 재사용되므로 요청이 끝나면 MDC.clear()로 비워야 하고, 비동기로 넘어가는 작업에는 값을 직접 전달해야 한다.

  • https://www.baeldung.com/mdc-in-log4j-2-logback

IOTA

IOTA는 IoT를 겨냥해 설계된 분산 원장이자 암호화폐다. Wikipedia 설명에 따르면 거래를 블록체인 대신 방향성 비순환 그래프(DAG, Tangle)에 저장하고, 채굴자 없이 새 거래를 내는 노드가 이전 거래 두 개를 승인하는 방식을 썼다. 현재 공식 사이트는 IOTA를 탈중앙 블록체인 인프라로 소개하고 있어 구조가 계속 바뀌어 왔다는 점도 확인했다.

  • https://www.iota.org/
  • https://www.forbes.com/advisor/investing/cryptocurrency/what-is-iota/
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다