Home > Backend Development > πŸ“š[Backend Development] CommentPath 및 ν•˜μœ„ λŒ“κΈ€ 생성 원리

πŸ“š[Backend Development] CommentPath 및 ν•˜μœ„ λŒ“κΈ€ 생성 원리
Backend Ddevelopment

β€œπŸ“š[Backend Development] CommentPath 및 ν•˜μœ„ λŒ“κΈ€ 생성 원리”

πŸ“ Intro

  • λŒ“κΈ€ μ‹œμŠ€ν…œμ—μ„œ 각 λŒ“κΈ€μ˜ 경둜(path)λ₯Ό κ΄€λ¦¬ν•˜λŠ” 방식은 계측적 ꡬ쑰λ₯Ό μœ μ§€ν•˜λ©΄μ„œλ„ λΉ λ₯΄κ²Œ 검색할 수 μžˆλ„λ‘ μ„€κ³„λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • λ³Έ κΈ€μ—μ„œλŠ” CommentPath 클래슀λ₯Ό λΆ„μ„ν•˜κ³ , ν•˜μœ„ λŒ“κΈ€μ΄ μƒμ„±λ˜λŠ” κ³Όμ •κ³Ό κ΄€λ ¨λœ λ‘œμ§μ„ μ„€λͺ…ν•©λ‹ˆλ‹€.

πŸ“Œ CommentPath 클래슀.

package kobe.board.comment.entity;

import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@ToString
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentPath {
	private String path;

	private static final String CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

	private static final int DEPTH_CHUNK_SIZE = 5;
	private static final int MAX_DEPTH = 5;

	// MIN_CHUNK = "00000", MAX_CHUNK = "zzzzz"
	private static final String MIN_CHUNK = String.valueOf(CHARSET.charAt(0)).repeat(DEPTH_CHUNK_SIZE);
	private static final String MAX_CHUNK = String.valueOf(CHARSET.charAt(CHARSET.length() - 1)).repeat(DEPTH_CHUNK_SIZE);

	public static CommentPath create(String path) {
		if (isDepthOverflowed(path)) {
			throw new IllegalStateException("depth overflowed");
		}

		CommentPath commentPath = new CommentPath();
		commentPath.path = path;
		return commentPath;
	}

	private static boolean isDepthOverflowed(String path) {
		return calculateDepth(path) > MAX_DEPTH;
	}

	private static int calculateDepth(String path) {
		// 25개의 λ¬Έμžμ—΄ / 5 = 5depth
		return path.length() / DEPTH_CHUNK_SIZE;
	}

	// CommentPath 클래슀의 path의 depthλ₯Ό κ΅¬ν•˜λŠ” λ§€μ„œλ“œ
	public int getDepth() {
		return calculateDepth(path);
	}

	// root인지 ν™•μΈν•˜λŠ” λ§€μ„œλ“œ
	public boolean isRoot() {
		// ν˜„μž¬μ˜ depthκ°€ 1인지 확인해주면 됨
		return calculateDepth(path) == 1;
	}

	// ν˜„μž¬ path의 parentPathλ₯Ό λ°˜ν™˜ν•΄μ£ΌλŠ” λ§€μ„œλ“œ
	public String getParentPath() {
		// 끝 5자리만 μž˜λΌλ‚΄λ©΄ 됨
		return path.substring(0, path.length() - DEPTH_CHUNK_SIZE);
	}

	// ν˜„μž¬ path의 ν•˜μœ„ λŒ“κΈ€μ˜ path을 λ§Œλ“œλŠ” λ§€μ„œλ“œ
	public CommentPath createChildCommentPath(String descendantsTopPath) {
		if (descendantsTopPath == null) {
			return CommentPath.create(path + MIN_CHUNK);
		}
		String childrenTopPath = findChildrenTopPath(descendantsTopPath);
		return CommentPath.create(increase(childrenTopPath));
	}

	// 00a0z0000200000 <- descendantsTopPath
	private String findChildrenTopPath(String descendantsTopPath) {
		return descendantsTopPath.substring(0, (getDepth() + 1) * DEPTH_CHUNK_SIZE);
	}

	private String increase(String path) {
		// pathμ—μ„œ κ°€μž₯ λ§ˆμ§€λ§‰ 5자리λ₯Ό 자λ₯Έ 것
		String lastChunk = path.substring(path.length() - DEPTH_CHUNK_SIZE);

		if (isChunkOverflowed(lastChunk)) {
			throw new IllegalStateException("chunk overflowed");
		}

		// Character set의 길이
		int charsetLength = CHARSET.length();

		// lastChunkλ₯Ό 10μ§„μˆ˜λ‘œ λ¨Όμ € λ³€ν™˜ν•˜κΈ° μœ„ν•œ 값을 μ €μž₯
		int value = 0;

		for (char character : lastChunk.toCharArray()) {
			value = value * charsetLength + CHARSET.indexOf(character);
		}

		value = value + 1;

		String result = "";

		for (int i = 0; i < DEPTH_CHUNK_SIZE; i++) {
			result = CHARSET.charAt(value % charsetLength) + result;
			value /= charsetLength;
		}

		return path.substring(0, path.length() - DEPTH_CHUNK_SIZE) + result;
	}

	private boolean isChunkOverflowed(String lastChunk) {
		return MAX_CHUNK.equals(lastChunk);
	}
}

βœ…1️⃣ CommentPath 클래슀 Intro

  • CommentPath ν΄λž˜μŠ€λŠ” λŒ“κΈ€μ˜ κ³ μœ ν•œ 경둜(path)λ₯Ό κ΄€λ¦¬ν•˜λŠ” 역할을 ν•©λ‹ˆλ‹€.
    • κ²½λ‘œλŠ” λ¬Έμžμ—΄λ‘œ ν‘œν˜„λ˜λ©°, 각 λŒ“κΈ€μ˜ depthλ₯Ό μœ μ§€ν•˜λ©΄μ„œ ν•˜μœ„ λŒ“κΈ€μ„ μ‰½κ²Œ 찾을 수 μžˆλ„λ‘ κ΅¬μ„±λ©λ‹ˆλ‹€.

πŸ“Œ1️⃣ μ£Όμš” μƒμˆ˜ 및 λ³€μˆ˜.

  • CHARSET : 0-9, A-Z, a-z둜 κ΅¬μ„±λœ 총 62개의 문자 μ…‹
  • DEPTH_CHUNK_SIZE = 5 : λŒ“κΈ€ 깊이λ₯Ό λ‚˜νƒ€λ‚΄λŠ” λ‹¨μœ„ 크기
  • MAX_DEPTH = 5 : λŒ“κΈ€μ˜ μ΅œλŒ€ 깊이
  • MIN_CHUNK = β€œ00000” : κ²½λ‘œμ—μ„œ μ‚¬μš©λ  μ΅œμ†Œ λ‹¨μœ„ λ¬Έμžμ—΄
  • MAX_CHUNK = β€œzzzzz” : κ²½λ‘œμ—μ„œ μ‚¬μš©λ  μ΅œλŒ€ λ‹¨μœ„ λ¬Έμžμ—΄

βœ…2️⃣ CommentPath.create(String path) λ©”μ„œλ“œ λ™μž‘ κ³Όμ •

  • 이 λ©”μ„œλ“œλŠ” μƒˆλ‘œμš΄ CommentPath 객체λ₯Ό μƒμ„±ν•˜λŠ” 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œμž…λ‹ˆλ‹€.

πŸ“Œ1️⃣ λ™μž‘ κ³Όμ •.

  • 1. isDepthOverflowed(path)λ₯Ό ν˜ΈμΆœν•˜μ—¬ path의 κΉŠμ΄κ°€ MAX_DEPTH = 5λ₯Ό μ΄ˆκ³Όν•˜λŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.
    • calculateDepth(path) > MAX_DEPTH이면 IllegalStateException을 λ˜μ§‘λ‹ˆλ‹€.
  • 2. CommentPath 객체λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
  • 3. 객체의 path ν•„λ“œλ₯Ό path둜 μ„€μ •ν•©λ‹ˆλ‹€.
  • 4. μƒˆλ‘œ μƒμ„±λœ CommentPath 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

βœ…3️⃣ CommentPath.createChildCommentPath(String descendantsTopPath) λ©”μ„œλ“œ λ™μž‘ κ³Όμ •

  • 이 λ©”μ„œλ“œλŠ” 주어진 descendantsTopPathλ₯Ό 기반으둜 μƒˆλ‘œμš΄ ν•˜μœ„ λŒ“κΈ€μ˜ pathλ₯Ό μƒμ„±ν•˜λŠ” 역할을 ν•©λ‹ˆλ‹€.

πŸ“Œ1️⃣ λ™μž‘ κ³Όμ •.

  • 1. descedantsTopPathκ°€ null이면 path + MIN_CHUNK(β€œ00000”)λ₯Ό μΆ”κ°€ν•˜μ—¬ CommentPathλ₯Ό μƒμ„±ν•˜κ³  λ°˜ν™˜ν•©λ‹ˆλ‹€.
  • 2. findChildrenTopPath(descendantsTopPath)λ₯Ό ν˜ΈμΆœν•˜μ—¬ childrenTopPathλ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.
    • descendantsTopPathμ—μ„œ depth + 1 길이만큼 λ¬Έμžμ—΄μ„ μžλ¦…λ‹ˆλ‹€.
  • 3. increase(childrenTopPath)λ₯Ό ν˜ΈμΆœν•˜μ—¬ 이전 ν•˜μœ„ λŒ“κΈ€ path κ°’μ—μ„œ 1을 μ¦κ°€μ‹œν‚΅λ‹ˆλ‹€.
  • 4. μ¦κ°€λœ childrenTopPathλ₯Ό 기반으둜 μƒˆλ‘œμš΄ CommentPath 객체λ₯Ό μƒμ„±ν•˜κ³  λ°˜ν™˜ν•©λ‹ˆλ‹€.

βœ…4️⃣ β€œ00000”이 μƒμ„±λ˜λ €λ©΄ μ–΄λ–€ 과정을 거쳐야 ν• κΉŒ?

  • 1. createChildCommentPath(null)이 ν˜ΈμΆœλ©λ‹ˆλ‹€.
  • 2. descendantsTopPathκ°€ nullμ΄λ―€λ‘œ path + MIN_CHUNKκ°€ CommentPath.creat()에 μ „λ‹¬λ©λ‹ˆλ‹€.
  • 3. CommentPath.create() λ‚΄λΆ€μ—μ„œ isDepthOverflowed 검사λ₯Ό ν†΅κ³Όν•˜λ©΄ μƒˆλ‘œμš΄ CommentPath 객체가 생성 λ©λ‹ˆλ‹€.
  • 4. path 값은 λΆ€λͺ¨ path + β€œ00000”이 λ©λ‹ˆλ‹€.

βœ…5️⃣ β€œ0000000000”이 μƒμ„±λ˜λ €λ©΄ μ–΄λ–€ 과정을 거쳐야 ν• κΉŒ?

  • 1. createdChildCommentPath(null)이 두 번 ν˜ΈμΆœλ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • 2. 첫 번째 호좜:
    • descendantsTopPath = null
    • path + β€œ00000”을 CommentPath.create()둜 μ „λ‹¬ν•˜μ—¬ β€œ00000”이 생성됨.
  • 3. 두 번째 호좜:
    • descendantsTopPath = β€œ00000”
    • β€œ00000β€μ˜ ν•˜μœ„ λŒ“κΈ€μ„ λ§Œλ“€ λ•Œ path + β€œ00000”을 μΆ”κ°€ν•˜μ—¬ β€œ0000000000”을 생성.
  • 즉, 2단계 ν•˜μœ„ λŒ“κΈ€ 생성 과정이 ν•„μš”ν•©λ‹ˆλ‹€.

βœ…6️⃣ νŠΉμ • descendantsTopPathκ°€ μ£Όμ–΄μ‘Œμ„ λ•Œ childrenTopPathκ°€ μ–΄λ–»κ²Œ λ³€ν• κΉŒμš”?

πŸ“Œ μ˜ˆμ‹œ: descendantsTopPath = β€œ00000”, childrenTopPath = β€œ00001”

  • 1. createChildCommentPath(β€œ00000”)κ°€ 호좜됨.
  • 2. findChildrenTopPath(β€œ00000”) 호좜:
    • descendantsTopPath = β€œ00000”
    • getDepth() + 1 = 2 β†’ depth 2 κΉŒμ§€μ˜ 5자리(00000)λ₯Ό κ°€μ Έμ˜΄.
    • childrenTopPath = β€œ00000”
  • 3. increase(β€œ00000”) μ‹€ν–‰:
    • 62μ§„μˆ˜ μ—°μ‚°μœΌλ‘œ β€œ00000” β†’ β€œ00001β€λ‘œ λ³€ν™˜.
  • 4. β€œ00001”이 path둜 μ„€μ •λœ CommentPath 객체가 생성됨.
  • 즉, increase(β€œ00000”) 연산을 톡해 β€œ00001”이 μƒμ„±λ©λ‹ˆλ‹€.

βœ…7️⃣ νŠΉμ • μƒν™©μ—μ„œ μƒˆλ‘œμš΄ λŒ“κΈ€μ˜ pathλ₯Ό μƒμ„±ν•˜λŠ” κ³Όμ •

πŸ“Œ μ˜ˆμ‹œ: descendantsTopPath = β€œ0000z”, ν•˜μœ„ λŒ“κΈ€μ΄ β€œabcdz” > β€œzzzzz” > β€œzzzzz” 일 λ•Œ, β€œabcdzβ€μ˜ sibling λŒ“κΈ€ 생성

  • 1. ν˜„μž¬ descendantsTopPathλŠ” β€œ0000zβ€μ΄λ―€λ‘œ 이 λŒ“κΈ€μ΄ μ†ν•œ ν•˜μœ„ λŒ“κΈ€λ“€μ΄ μ‘΄μž¬ν•¨.
  • 2. createChildCommentPath(β€œzzzzz”) μ‹€ν–‰ β†’ κ°€μž₯ 큰 ν•˜μœ„ pathλ₯Ό κΈ°μ€€μœΌλ‘œ μƒˆλ‘œμš΄ pathλ₯Ό 생성해야 함.
  • 3. findChildCommentTopPaht(β€œzzzzz”) μ‹€ν–‰:
    • β€œzzzzzβ€μ˜ depth + 1 κΈΈμ΄κΉŒμ§€ μžλ¦„ β†’ β€œzzzzz”
  • 4. increase(β€œzzzzz”) μ‹€ν–‰:
    • β€œzzzzzβ€μ—μ„œ μ¦κ°€λœ κ°’ β€œaaaaa”가 λ‚˜μ˜΄
    • ν•˜μ§€λ§Œ β€œabcdz” 와 같은 depth의 μƒˆλ‘œμš΄ λŒ“κΈ€μ„ λ§Œλ“€λ €λ©΄ β€œabcdzβ€μ˜ sibling λŒ“κΈ€μ΄ λ˜μ–΄μ•Ό 함.
  • 5. increase(β€œabcdz”) μ‹€ν–‰:
    • β€œabcdzβ€μ—μ„œ μ¦κ°€λœ κ°’ β€œabce0”가 생성됨.
  • πŸ“Œ κ²°λ‘  : μ΅œμ’…μ μœΌλ‘œ 생성될 β€œabce0β€μ˜ β€œpathβ€λŠ” λΆ€λͺ¨ β€œpath” + β€œabce0” 즉, path = β€œ0000zabce0”이 λ©λ‹ˆλ‹€.

βœ…8️⃣ κ²°λ‘ 

  • CommentPathλ₯Ό μ΄μš©ν•˜λ©΄ 계측적 λŒ“κΈ€ μ‹œμŠ€ν…œμ„ 효과적으둜 κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • ν•˜μœ„ λŒ“κΈ€μ˜ pathλ₯Ό 62μ§„μˆ˜ λ¬Έμžμ—΄ 증가 λ°©μ‹μœΌλ‘œ κ΄€λ¦¬ν•˜μ—¬, λΉ λ₯Έ μ •λ ¬ 및 검색이 κ°€λŠ₯ν•©λ‹ˆλ‹€.
  • increase 연산을 톡해 ν•˜μœ„ λŒ“κΈ€μ„ λ™μ μœΌλ‘œ μƒμ„±ν•˜λ©°, 경둜 μ˜€λ²„ν”Œλ‘œμš°λ₯Ό 방지할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ΄λŸ¬ν•œ 방식은 트리 ꡬ쑰λ₯Ό 효율적으둜 μ €μž₯ν•˜κ³  μ‘°νšŒν•˜λŠ” λ°©λ²•μœΌλ‘œ, λŒ€κ·œλͺ¨ λŒ“κΈ€ μ‹œμŠ€ν…œμ—μ„œλ„ μœ μš©ν•˜κ²Œ 적용될 수 μžˆμŠ΅λ‹ˆλ‹€.