Guava Table로 고차원 데이터 모델링하기

Table의 한계와 확장 전략

Guava의 Table은 행과 열로 구성된 2차원 자료구조입니다. 실무에서는 시간, 공간, 카테고리 등 여러 기준으로 데이터를 분류해야 하는 경우가 빈발하는데, 이때 단순 2차원 구조로는 표현이 어렵습니다. Table 자체는 고차원을 지원하지 않지만, 적절한 조합을 통해 n차원 데이터를 우회적으로 구현할 수 있습니다.

중첩 Table로 3차원 구현

셀 단위에 또 다른 Table을 배치하면 세 개의 인덱스로 데이터를 조회할 수 있습니다. 예를 들어 Table<R, C, V>의 값 타입 VTable<C2, R2, V2>로 대치하는 방식입니다.

일별 지역별 매출·이익 데이터를 관리하는 시나리오를 살펴보겠습니다.

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.junit.jupiter.api.Test;

public class TripleIndexTableTest {

    @Test
    void threeDimensionalLookup() {
        // 1차 인덱스: 날짜, 2차 인덱스: 지역, 3차 인덱스: 지표명 → 값
        Table<String, String, Table<String, Integer>> timeSeriesCube = HashBasedTable.create();

        Table<String, Integer> northMetrics = HashBasedTable.create();
        northMetrics.put("Revenue", 1_000_000);
        northMetrics.put("Margin", 250_000);

        Table<String, Integer> southMetrics = HashBasedTable.create();
        southMetrics.put("Revenue", 800_000);
        southMetrics.put("Margin", 180_000);

        timeSeriesCube.put("2024-12-01", "North", northMetrics);
        timeSeriesCube.put("2024-12-01", "South", southMetrics);

        // 3차원 조회: 특정일 → 특정지역 → 특정지표
        int northRevenue = timeSeriesCube.get("2024-12-01", "North").get("Revenue");
        System.out.println("North revenue: " + northRevenue);
    }
}

순회 패턴 설계

중첩된 구조를 순회할 때는 외부 키 집합을 먼저 확보하고, 내부 Table로 진입하여 재차 순회하는 방식이 자연스럽습니다.

@Test
void iterateNestedLayers() {
    Table<String, String, Table<String, Integer>> cube = buildSampleCube();

    for (String date : cube.rowKeySet()) {
        for (String zone : cube.columnKeySet()) {
            Table<String, Integer> metrics = cube.get(date, zone);
            if (metrics == null) continue;

            metrics.cellSet().forEach(cell -> 
                System.out.printf("[%s / %s] %s = %d%n", 
                    date, zone, cell.getRowKey(), cell.getValue())
            );
        }
    }
}

cellSet()을 활용하면 행키·열키·값을 동시에 얻어 중첩 반복문을 줄일 수 있습니다.

4차원 이상으로 확장: Map과의 결합

차원이 늘어나면 Table만으로는 부족합니다. 최상위 인덱스를 Map으로 분리하고, 그 값에 Table을 매핑하면 4차원, 5차원도 가능합니다.

@Test
void fourDimensionalStructure() {
    // 1차: 연도(Map) → 2차: 월, 3차: 지역(Table) → 4차: 지표명, 값
    Map<String, Table<String, String, Table<String, Integer>>> hyperCube = 
        new HashMap<>();

    Table<String, Integer> productMetrics = HashBasedTable.create();
    productMetrics.put("UnitsSold", 5_000);
    productMetrics.put("ReturnRate", 120);

    Table<String, String, Table<String, Integer>> monthlySlice = 
        HashBasedTable.create();
    monthlySlice.put("Seoul", "Electronics", productMetrics);

    hyperCube.put("2024", monthlySlice);

    // 조회 경로: 연도 → 워치(여기서는 미사용) → 지역/카테고리 → 지표
    Table<String, Integer> found = hyperCube.get("2024")
                                            .get("Seoul", "Electronics");
    System.out.println("Units sold: " + found.get("UnitsSold"));
}

위 예시는 연도-월-지역-카테고리-지표 다섯 개 축을 두 단계 Map과 두 단계 Table로 분산 배치한 형태입니다. 핵심은 각 계층의 책임을 명확히 분리하여 조회 경로를直관적으로 만드는 것입니다.

설계 시 고려사항

  • 타입 안전성: 깊이 중첩될수록 제네릭 선언이 복잡해지므로, 의미 있는 타입 별칭(예: class RegionTable extends HashBasedTable<...>)을 고려하세요.
  • Null 방어: 중간 계층에서 null이 반환되면 하위 접근 시 NPE가 발생합니다. Table<R, C, V>get 결과에 대해 Optional 또는 기본값 객체를 활용하세요.
  • 메모리 오버헤드: 빈 셀을 포함한 완전한 다차원 배열 대비 희소 행렬(sparse matrix) 형태이므로, 데이터 밀도가 극도로 낮은 경우 오히려 유리합니다.

태그: Guava Table Java Collections Multidimensional Data HashBasedTable

6월 7일 01:24에 게시됨