Apache Kafka용 Pulsar 어댑터

Apache Kafka용 Pulsar 어댑터

Pulsar는 현재 Apache Kafka Java 클라이언트 API로 작성된 애플리케이션을 위한 쉬운 선택지를 제공해요. 기존 Kafka 코드를 거의 그대로 두고 Pulsar로 옮길 수 있도록 Pulsar Kafka 래퍼(wrapper)를 제공하는데요. 다만 이 래퍼를 사용하기 전에 몇 가지 버전 규칙을 알아두는 게 좋아요.

출처: 문서

본문

note

Pulsar Kafka 래퍼 아티팩트(pulsar-client-kafkapulsar-client-kafka-original)는 별도의 apache/pulsar-adapters 리포지토리에 있으며, Maven Central에 게시된 마지막 릴리스 버전은 2.11.0이에요. 더 새로운 Pulsar 브로커(3.x / 4.x)를 실행하더라도 의존성은 2.11.0에 고정해 두세요 — 더 새로운 대응 아티팩트는 게시되지 않으니까요.

Pulsar Kafka 호환 래퍼 사용하기 (Use the Pulsar Kafka compatibility wrapper)

Pulsar Kafka 호환 래퍼를 사용하려면 다음 단계를 완료해요.

Step 1: 기존 애플리케이션에서 일반 Kafka 클라이언트 의존성을 Pulsar Kafka 래퍼로 교체해요. pom.xml의 다음 의존성을 제거하세요.

<dependency>
  <groupId>org.apache.kafka</groupId>
  <artifactId>kafka-clients</artifactId>
  <version>0.10.2.1</version>
</dependency>

Step 2: 그다음 Pulsar Kafka 래퍼에 대한 다음 의존성을 포함시키세요.

<dependency>
  <groupId>org.apache.pulsar</groupId>
  <artifactId>pulsar-client-kafka</artifactId>
  <version>2.11.0</version>
</dependency>

새 의존성을 사용하면 기존 코드가 변경 없이 동작해요. 설정을 조정해서 프로듀서와 컨슈머가 Kafka 대신 Pulsar 서비스를 가리키게 하고, 특정 Pulsar 토픽을 사용하도록 해야 해요.

기존 Kafka 클라이언트와 함께 Pulsar Kafka 래퍼 사용하기

Kafka에서 Pulsar로 마이그레이션할 때, 애플리케이션이 마이그레이션 기간 동안 원래 Kafka 클라이언트와 Pulsar Kafka 래퍼를 함께 사용할 수도 있어요. 이 경우 unshaded(언섀도) Pulsar Kafka 클라이언트 래퍼를 사용하는 것을 고려해야 해요.

<dependency>
  <groupId>org.apache.pulsar</groupId>
  <artifactId>pulsar-client-kafka-original</artifactId>
  <version>2.11.0</version>
</dependency>

이 의존성을 사용할 때는 프로듀서를 org.apache.kafka.clients.producer.KafkaProducer 대신 org.apache.kafka.clients.producer.PulsarKafkaProducer로, 컨슈머는 org.apache.kafka.clients.producer.PulsarKafkaConsumer로 생성해요.

프로듀서 예제 (Producer example)

// Topic needs to be a regular Pulsar topic
String topic = "persistent://public/default/my-topic";

Properties props = new Properties();
// Point to a Pulsar service
props.put("bootstrap.servers", "pulsar://localhost:6650");

props.put("key.serializer", IntegerSerializer.class.getName());
props.put("value.serializer", StringSerializer.class.getName());

Producer<Integer, String> producer = new KafkaProducer(props);

for (int i = 0; i < 10; i++) {
    producer.send(new ProducerRecord<Integer, String>(topic, i, "hello-" + i));
    log.info("Message {} sent successfully", i);
}

producer.close();

컨슈머 예제 (Consumer example)

String topic = "persistent://public/default/my-topic";

Properties props = new Properties();
// Point to a Pulsar service
props.put("bootstrap.servers", "pulsar://localhost:6650");
props.put("group.id", "my-subscription-name");
props.put("enable.auto.commit", "false");
props.put("key.deserializer", IntegerDeserializer.class.getName());
props.put("value.deserializer", StringDeserializer.class.getName());

Consumer<Integer, String> consumer = new KafkaConsumer(props);
consumer.subscribe(Arrays.asList(topic));

while (true) {
    ConsumerRecords<Integer, String> records = consumer.poll(100);
    records.forEach(record -> {
        log.info("Received record: {}", record);
    });

    // Commit last offset
    consumer.commitSync();
}

전체 예제 (Complete Examples)

완전한 프로듀서와 컨슈머 예제는 여기에서 찾을 수 있어요.

호환성 매트릭스 (Compatibility matrix)

현재 Pulsar Kafka 래퍼는 Kafka API가 제공하는 대부분의 연산을 지원해요.

프로듀서 (Producer)

APIs:

Producer Method Supported Notes
Future<RecordMetadata> send(ProducerRecord<K, V> record) Yes
Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) Yes
void flush() Yes
List<PartitionInfo> partitionsFor(String topic) No
Map<MetricName, ? extends Metric> metrics() No
void close() Yes
void close(long timeout, TimeUnit unit) Yes

Properties:

Config property Supported Notes
acks Ignored 내구성과 쿼럼 쓰기는 네임스페이스 수준에서 설정돼요
auto.offset.reset Yes 특정 설정을 주지 않으면 기본값 earliest를 사용해요
batch.size Ignored
bootstrap.servers Yes
buffer.memory Ignored
client.id Ignored
compression.type Yes gzip과 lz4를 허용해요. snappy는 안 돼요
connections.max.idle.ms Yes 최대 2,147,483,647,000(Integer.MAX_VALUE * 1000) ms의 유휴 시간만 지원해요
interceptor.classes Yes
key.serializer Yes
linger.ms Yes 메시지를 배치할 때 그룹 커밋 시간을 제어해요
max.block.ms Ignored
max.in.flight.requests.per.connection Ignored Pulsar에서는 여러 요청이 진행 중이어도 순서가 유지돼요
max.request.size Ignored
metric.reporters Ignored
metrics.num.samples Ignored
metrics.sample.window.ms Ignored
partitioner.class Yes
receive.buffer.bytes Ignored
reconnect.backoff.ms Ignored
request.timeout.ms Ignored
retries Ignored Pulsar 클라이언트는 send 타임아웃이 만료될 때까지 지수 백오프로 재시도해요
send.buffer.bytes Ignored
timeout.ms Yes
value.serializer Yes

컨슈머 (Consumer)

다음 표는 컨슈머 API를 나열한 것이에요.

Consumer Method Supported Notes
Set<TopicPartition> assignment() No
Set<String> subscription() Yes
void subscribe(Collection<String> topics) Yes
void subscribe(Collection<String> topics, ConsumerRebalanceListener callback) No
void assign(Collection<TopicPartition> partitions) No
void subscribe(Pattern pattern, ConsumerRebalanceListener callback) No
void unsubscribe() Yes
ConsumerRecords<K, V> poll(long timeoutMillis) Yes
void commitSync() Yes
void commitSync(Map<TopicPartition, OffsetAndMetadata> offsets) Yes
void commitAsync() Yes
void commitAsync(OffsetCommitCallback callback) Yes
void commitAsync(Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback) Yes
void seek(TopicPartition partition, long offset) Yes
void seekToBeginning(Collection<TopicPartition> partitions) Yes
void seekToEnd(Collection<TopicPartition> partitions) Yes
long position(TopicPartition partition) Yes
OffsetAndMetadata committed(TopicPartition partition) Yes
Map<MetricName, ? extends Metric> metrics() No
List<PartitionInfo> partitionsFor(String topic) No
Map<String, List<PartitionInfo>> listTopics() No
Set<TopicPartition> paused() No
void pause(Collection<TopicPartition> partitions) No
void resume(Collection<TopicPartition> partitions) No
Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes(Map<TopicPartition, Long> timestampsToSearch) No
Map<TopicPartition, Long> beginningOffsets(Collection<TopicPartition> partitions) No
Map<TopicPartition, Long> endOffsets(Collection<TopicPartition> partitions) No
void close() Yes
void close(long timeout, TimeUnit unit) Yes
void wakeup() No

Properties:

Config property Supported Notes
group.id Yes Pulsar 구독 이름에 매핑돼요
max.poll.records Yes
max.poll.interval.ms Ignored 메시지는 브로커에서 "푸시"돼요
session.timeout.ms Ignored
heartbeat.interval.ms Ignored
bootstrap.servers Yes 단일 Pulsar 서비스 URL을 가리켜야 해요
enable.auto.commit Yes
auto.commit.interval.ms Ignored auto-commit을 사용하면 ack가 즉시 브로커로 보내져요
partition.assignment.strategy Ignored
auto.offset.reset Yes earliest와 latest만 지원해요
fetch.min.bytes Ignored
fetch.max.bytes Ignored
fetch.max.wait.ms Ignored
interceptor.classes Yes
metadata.max.age.ms Ignored
max.partition.fetch.bytes Ignored
send.buffer.bytes Ignored
receive.buffer.bytes Ignored
client.id Ignored

Pulsar 설정 커스터마이징 (Customize Pulsar configurations)

Kafka 프로퍼티에서 직접 Pulsar 인증 제공자(auth provider)를 구성할 수 있어요.

Pulsar 클라이언트 프로퍼티 (Pulsar client properties)

Config property Default Notes
pulsar.authentication.class 인증 제공자로 설정해요. 예를 들어 org.apache.pulsar.client.impl.auth.AuthenticationTls
pulsar.authentication.params.map 인증 플러그인의 파라미터를 나타내는 Map
pulsar.authentication.params.string 인증 플러그인의 파라미터를 나타내는 문자열, 예를 들어 key1:val1,key2:val2
pulsar.use.tls false TLS 전송 암호화를 활성화해요
pulsar.tls.trust.certs.file.path TLS 신뢰 인증서 저장소 경로
pulsar.tls.allow.insecure.connection false 브로커의 자체 서명 인증서를 허용해요
pulsar.operation.timeout.ms 30000 일반 연산 타임아웃
pulsar.stats.interval.seconds 60 Pulsar 클라이언트 라이브러리 통계 출력 간격
pulsar.num.io.threads 1 사용할 Netty IO 스레드 수
pulsar.connections.per.broker 1 각 브로커에 대한 최대 연결 수
pulsar.use.tcp.nodelay true TCP no-delay
pulsar.concurrent.lookup.requests 50000 최대 동시 토픽 룩업 수
pulsar.max.number.rejected.request.per.connection 50 연결을 강제로 닫는 오류 임계값
pulsar.keepalive.interval.ms 30000 각 클라이언트-브로커 연결의 keep-alive 간격

Pulsar 프로듀서 프로퍼티 (Pulsar producer properties)

Config property Default Notes
pulsar.producer.name 프로듀서 이름을 지정해요
pulsar.producer.initial.sequence.id 이 프로듀서의 시퀀스 ID 기준값을 지정해요
pulsar.producer.max.pending.messages 1000 브로커의 ack를 기다리는 메시지 큐의 최대 크기
pulsar.producer.max.pending.messages.across.partitions 50000 모든 파티션에 걸친 최대 대기(pending) 메시지 수
pulsar.producer.batching.enabled true 프로듀서의 메시지 자동 배치(batching) 활성화 여부를 제어해요
pulsar.producer.batching.max.messages 1000 배치 내 최대 메시지 수
pulsar.block.if.producer.queue.full 큐가 가득 찼을 때 프로듀서를 블록할지 지정해요
pulsar.crypto.reader.factory.class.name 프로듀서가 CryptoKeyReader를 만들 수 있게 하는 CryptoReader-Factory(CryptoKeyReaderFactory) 클래스 이름을 지정해요

Pulsar 컨슈머 프로퍼티 (Pulsar consumer Properties)

Config property Default Notes
pulsar.consumer.name 컨슈머 이름을 지정해요
pulsar.consumer.receiver.queue.size 1000 컨슈머 수신 큐 크기
pulsar.consumer.acknowledgments.group.time.millis 100 컨슈머가 브로커로 ack를 보낼 최대 그룹 시간
pulsar.consumer.total.receiver.queue.size.across.partitions 50000 파티션에 걸친 최대 총 수신 큐 크기
pulsar.consumer.subscription.topics.mode PersistentOnly 컨슈머의 구독 토픽 모드
pulsar.crypto.reader.factory.class.name 컨슈머가 CryptoKeyReader를 만들 수 있게 하는 CryptoReader-Factory(CryptoKeyReaderFactory) 클래스 이름을 지정해요

더 알아보기 (Learn more)

  • Kafka에서 Pulsar로 옮겨오는 마이그레이션 전략을 더 보고 싶다면 Pulsar Kafka 호환 관련 문서를 참고해요.
  • 프로듀서와 컨슈머의 전체 예제는 Pulsar GitHub 리포지토리에서 확인할 수 있어요.
  • Pulsar 인증(TLS) 설정에 대해 알아보려면 인증 관련 문서를 살펴보세요.
  • 토픽과 구독 개념이 궁금하다면 핵심 개념 문서를 참고해요.