홈으로 돌아가기

Flink 실시간 JOIN: delete+insert를 사용한 업데이트 처리

이 기사는 키 변경 시 Apache Flink 실시간 JOIN의 업데이트 문제를 분석합니다. Debezium을 사용한 delete+insert로 업데이트 분할하는 범용 해결책 제안. 전체 코드와 상태 분석.

Flink JOIN 업데이트 해결책: 상태 누출 없는 delete+insert
Advertisement 728x90

Apache Flink 실시간 JOIN에서 업데이트 처리: 상태 누수 문제 해결

Apache Flink 실시간 JOIN 시스템에서 소스 테이블 레코드의 업데이트는 상태 누수와 잘못된 결과를 초래할 수 있습니다. domains 테이블의 user_id 필드가 변경되면, 업데이트 이벤트가 이전 데이터를 지우지 않고 다른 TaskManager에 할당될 수 있습니다. 마찬가지로 기본 키 변경 시 이전 레코드가 제거되지 않아 하류에서 중복이 발생합니다.

상황을 세부적으로 살펴보겠습니다:

  • domains.id는 고정하고 user_id만 변경: 새 이벤트가 다른 키로 해시되어 이전 상태가 남습니다.
  • domains.iduser_id 모두 변경: 새 레코드가 생성되지만 이전 레코드가 제거되지 않아 중복 제거가 깨집니다.

Flink의 분산 아키텍처와 로컬 존

Flink는 keyBy를 통해 데이터를 TaskManager에 분산합니다. 키의 해시 값이 소유 컨테이너를 결정하며, 이를 통해:

Google AdInline article slot
  • 동일 키의 모든 이벤트가 한 곳에 모입니다.
  • 이벤트 순서가 보장됩니다.

해시 공간은 균등하게 분배되며, 각 TaskManager가 자신의 존을 담당합니다. keyBy 없이 라운드 로빈으로 처리하면 JOIN에 부적합합니다—지역성 보장이 사라지기 때문입니다.

만능 해결책: 삭제 + 삽입

업데이트를 처리하기 위해 Debezium CDC 이벤트를 삭제(before)와 삽입(after)로 분리합니다. 순서가 중요합니다: 먼저 이전 키로 삭제, 그다음 새 키로 삽입. keyBy가 순차 처리를 보장합니다.

Domain 클래스 구현:

Google AdInline article slot
public class Domain implements Serializable {
  public Integer id;
  public Integer user_id;
  public String domain_name;
  public boolean delete;

  // getters and setters omitted

  public static Domain[] fromRow(Row row) {
        Character op = (Character) row.getField(1);

        if (op == null) {
            throw new IllegalStateException("Never should happen, if Debezium feels fine");
        }

        if (op == 'd') {
            Row before = (Row) row.getField(0);
    
            return new Domain[]{
              new Domain(before.getField(0), before.getField(1), before.getField(2), true)            };
        } else if (op == 'u') {
            Row before = (Row) row.getField(0);
            Row after = (Row) row.getField(2);
    
            return new Domain[]{
              new Domain(before.getField(0), before.getField(1), before.getField(2), true),
              new Domain(after.getField(0), after.getField(1), after.getField(2), false)
            };
        } else {
            Row after = (Row) row.getField(2);
    
            return new Domain[]{
              new Domain(after.getField(0), after.getField(1), after.getField(2), false)
            };
        }
    }
}

로직:

  • 삭제 ('d'): before에서 삭제 이벤트 생성.
  • 업데이트 ('u'): before에서 삭제 + after에서 삽입.
  • 삽입 ('c'): after에서 삽입만.

삭제는 상태에서 레코드를 제거하거나 표시하고, 삽입은 업데이트합니다. 가변 JOIN 또는 PK 키에 대해 이벤트가 상태를 스스로 정리합니다.

이 접근법의 장점

  • 실시간 스트림에서 완전한 CRUD 지원.
  • 누수 없고 중복 없는 상태.
  • 가변 JOIN 및 PK 키 적절 처리.
  • keyBy로 순서 보장.

핵심 요약

  • 업데이트를 삭제+삽입으로 분리하면 키 변경 시 상태 누수 해결.
  • 삽입 전에 삭제가 일관성을 위한 핵심.
  • Debezium의 before/after로 이전 값 접근.
  • Flink 분산 환경에서 원활히 확장.

— Editorial Team

Google AdInline article slot
Advertisement 728x90

다음 읽기