jetstream-api 0.7.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/PKG-INFO +12 -1
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/README.md +11 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/__init__.py +1 -1
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/producer.py +211 -98
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/qmgr.py +1 -1
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/jetstream_api.egg-info/PKG-INFO +12 -1
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/pyproject.toml +1 -1
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/LICENSE.txt +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/admin.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/admintopic.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/common.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/connectivity.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/exception.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/msg.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/partitioner.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/pattern.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/properties.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/py.typed +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/topic.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/ilink/util.py +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/jetstream_api.egg-info/SOURCES.txt +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/jetstream_api.egg-info/dependency_links.txt +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/jetstream_api.egg-info/requires.txt +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/jetstream_api.egg-info/top_level.txt +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/setup.cfg +0 -0
- {jetstream_api-0.7.1 → jetstream_api-0.7.2}/tests/test_example.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jetstream-api
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Python client API for iLink M.O.M. (Message Oriented Middleware)
|
|
5
5
|
Author: snowjeans
|
|
6
6
|
License: Boost Software License - Version 1.0 - August 17th, 2003
|
|
@@ -54,6 +54,17 @@ Python으로 작성된 ILink 클라이언트 API입니다.
|
|
|
54
54
|
## 설치 방법
|
|
55
55
|
pip install jetstream-api
|
|
56
56
|
|
|
57
|
+
## 0.7.2 변경 - batchSize 기본값 262144 로 되돌림, 발행 경로 CPU 절감 (자바 v2.3.2 와 같음)
|
|
58
|
+
|
|
59
|
+
- `batchSize` 기본값을 **262144** 로 되돌렸습니다. 0.7.1 의 16384 에서 파이썬 클라는 처리량이 24~35% 낮았습니다(GIL 이
|
|
60
|
+
천장이라 배치마다 드는 비용이 그대로 처리량에서 빠집니다). 멱등 기본 켜짐은 그대로입니다.
|
|
61
|
+
- 발행 경로 CPU 를 줄였습니다(API 는 그대로입니다). 레코드 future 가 배치 결과 하나를 함께 가리키고(완결은 배치당 한 번),
|
|
62
|
+
키 -> 파티션 결과를 캐시하고, `send()` 가 설정값을 레코드마다 다시 읽지 않고, I/O 스레드를 깨우는 신호를 합치고,
|
|
63
|
+
ACK 하나의 배치 완결을 잠금 한 번에 묶습니다.
|
|
64
|
+
- producer 는 만들 때 설정 객체를 **복사**합니다(자바 v2.3.2 도 같고, Kafka 와 같습니다). 만든 뒤 넘긴 객체를 바꿔도 그
|
|
65
|
+
producer 에는 반영되지 않습니다 - 설정을 바꾸려면 producer 를 새로 만드십시오.
|
|
66
|
+
- 문서: 멱등이 거르는 것은 producer 안의 재전송뿐입니다 - 앱이 `send` 를 다시 부르면 새 레코드로 적재됩니다.
|
|
67
|
+
|
|
57
68
|
## 0.7.1 변경 - producer 기본값: 멱등 켜짐, batchSize 16384 (자바 v2.3.1 과 같음)
|
|
58
69
|
|
|
59
70
|
- `enableIdempotence` 기본값이 **켜짐**입니다. 멱등을 직접 정하지 않았으면 `acks(0)` 이나 `maxInFlight` 가 5 를 넘을 때
|
|
@@ -8,6 +8,17 @@ Python으로 작성된 ILink 클라이언트 API입니다.
|
|
|
8
8
|
## 설치 방법
|
|
9
9
|
pip install jetstream-api
|
|
10
10
|
|
|
11
|
+
## 0.7.2 변경 - batchSize 기본값 262144 로 되돌림, 발행 경로 CPU 절감 (자바 v2.3.2 와 같음)
|
|
12
|
+
|
|
13
|
+
- `batchSize` 기본값을 **262144** 로 되돌렸습니다. 0.7.1 의 16384 에서 파이썬 클라는 처리량이 24~35% 낮았습니다(GIL 이
|
|
14
|
+
천장이라 배치마다 드는 비용이 그대로 처리량에서 빠집니다). 멱등 기본 켜짐은 그대로입니다.
|
|
15
|
+
- 발행 경로 CPU 를 줄였습니다(API 는 그대로입니다). 레코드 future 가 배치 결과 하나를 함께 가리키고(완결은 배치당 한 번),
|
|
16
|
+
키 -> 파티션 결과를 캐시하고, `send()` 가 설정값을 레코드마다 다시 읽지 않고, I/O 스레드를 깨우는 신호를 합치고,
|
|
17
|
+
ACK 하나의 배치 완결을 잠금 한 번에 묶습니다.
|
|
18
|
+
- producer 는 만들 때 설정 객체를 **복사**합니다(자바 v2.3.2 도 같고, Kafka 와 같습니다). 만든 뒤 넘긴 객체를 바꿔도 그
|
|
19
|
+
producer 에는 반영되지 않습니다 - 설정을 바꾸려면 producer 를 새로 만드십시오.
|
|
20
|
+
- 문서: 멱등이 거르는 것은 producer 안의 재전송뿐입니다 - 앱이 `send` 를 다시 부르면 새 레코드로 적재됩니다.
|
|
21
|
+
|
|
11
22
|
## 0.7.1 변경 - producer 기본값: 멱등 켜짐, batchSize 16384 (자바 v2.3.1 과 같음)
|
|
12
23
|
|
|
13
24
|
- `enableIdempotence` 기본값이 **켜짐**입니다. 멱등을 직접 정하지 않았으면 `acks(0)` 이나 `maxInFlight` 가 5 를 넘을 때
|
|
@@ -12,6 +12,7 @@ producer 하나 = 토픽 하나 = 전용 연결 하나 + I/O 스레드 하나다
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import collections
|
|
15
|
+
import copy
|
|
15
16
|
import itertools
|
|
16
17
|
import os
|
|
17
18
|
import random
|
|
@@ -31,6 +32,10 @@ from .partitioner import partition_for_key
|
|
|
31
32
|
|
|
32
33
|
#: retries 기본값. 사실상 무제한 재시도를 뜻한다.
|
|
33
34
|
_INT_MAX = 2147483647
|
|
35
|
+
#: 키 -> 파티션 캐시에 둘 키 수 상한(producer 당). 차면 비우고 다시 채운다
|
|
36
|
+
_KEY_CACHE_MAX = 16384
|
|
37
|
+
#: 키 -> 파티션 캐시에 담을 키 길이 상한(바이트). 긴 키는 매번 해시한다(캐시 메모리를 묶어 둔다)
|
|
38
|
+
_KEY_CACHE_MAX_KEY = 256
|
|
34
39
|
|
|
35
40
|
|
|
36
41
|
class ILProducerConfig:
|
|
@@ -58,9 +63,9 @@ class ILProducerConfig:
|
|
|
58
63
|
self._acks = 1
|
|
59
64
|
#: 배치를 모으는 시간(ms). 0 이면 모이는 대로 즉시 보낸다
|
|
60
65
|
self._lingerMs = 0
|
|
61
|
-
# 0.7.
|
|
62
|
-
#: 파티션 배치 하나의 크기 상한(바이트). 기본
|
|
63
|
-
self._batchSize =
|
|
66
|
+
# 0.7.2: 262144 로 되돌렸다(사용자 결정 2026-09-13). 0.7.1 의 16384 에서 24~35% 느렸다(GIL 천장)
|
|
67
|
+
#: 파티션 배치 하나의 크기 상한(바이트). 기본 262144
|
|
68
|
+
self._batchSize = 262144
|
|
64
69
|
#: 동시에 ACK 를 기다릴 봉투 수 상한
|
|
65
70
|
self._maxInFlight = 5
|
|
66
71
|
#: 연결 실패 시 재시도 횟수
|
|
@@ -135,7 +140,7 @@ class ILProducerConfig:
|
|
|
135
140
|
self._lingerMs = max(0, n); return self
|
|
136
141
|
|
|
137
142
|
def batchSize(self, n: int) -> "ILProducerConfig":
|
|
138
|
-
"""파티션 배치 하나의 크기 상한(바이트)을 정한다. (기본
|
|
143
|
+
"""파티션 배치 하나의 크기 상한(바이트)을 정한다. (기본 262144 = 256KB)
|
|
139
144
|
|
|
140
145
|
Args:
|
|
141
146
|
n (int): 상한(**바이트**). 배치는 파티션마다 따로 모인다. 레코드를 붙이면 이 값을
|
|
@@ -145,7 +150,8 @@ class ILProducerConfig:
|
|
|
145
150
|
ILProducerConfig: 체이닝을 위해 자기 자신.
|
|
146
151
|
|
|
147
152
|
Note:
|
|
148
|
-
뜻과 기본값이 Java 클라이언트와 같다(0.7.
|
|
153
|
+
뜻과 기본값이 Java 클라이언트와 같다(0.7.2 에서 262144 로 되돌렸다. 0.7.1 은 16384 였고 파이썬은
|
|
154
|
+
그 값에서 24~35% 느렸다).
|
|
149
155
|
``lingerMs`` 가 0 이면 자르는 크기로만 쓰이고(배치는 보낼 수 있게 되는 즉시 나간다),
|
|
150
156
|
``lingerMs`` 가 0 보다 크면 전송 조건도 된다(이만큼 차거나 lingerMs 가 지나면 보낸다).
|
|
151
157
|
레코드 하나가 이 값보다 크면 그 레코드 혼자 배치 하나가 된다. 보낼 때는 준비된
|
|
@@ -194,8 +200,10 @@ class ILProducerConfig:
|
|
|
194
200
|
보낸다(순서 유지). 재시도 총 시간은 :meth:`deliveryTimeoutMs` 로도 함께 제한된다.
|
|
195
201
|
|
|
196
202
|
Warning:
|
|
197
|
-
재시도는 **중복 적재를 만들 수 있다.** ACK 를
|
|
198
|
-
못 한 것은 구분되지 않는다.
|
|
203
|
+
비멱등(``enableIdempotence(False)``)이면 재시도는 **중복 적재를 만들 수 있다.** ACK 를
|
|
204
|
+
못 받은 것과 서버가 적재를 못 한 것은 구분되지 않는다. 멱등(기본 켜짐)이면 producer 안의
|
|
205
|
+
재시도는 엔진이 걸러 한 번만 적재된다. 앱이 ``send`` 를 다시 부르는 재발행은 새 레코드라
|
|
206
|
+
멱등이어도 걸러지지 않는다.
|
|
199
207
|
"""
|
|
200
208
|
self._retries = max(0, n); return self
|
|
201
209
|
|
|
@@ -321,7 +329,8 @@ class ILProducerConfig:
|
|
|
321
329
|
"""멱등 발행(중복 제거)을 켜거나 끈다. (기본 켜짐 - 0.7.1)
|
|
322
330
|
|
|
323
331
|
켜면 접속 시 서버에서 PID 를 발급받아 파티션 배치마다 (PID, baseSeq) 를 실어 보낸다.
|
|
324
|
-
재시도로 같은 배치가 두 번 도착해도 서버가 시퀀스로 걸러 **한 번만 적재한다.**
|
|
332
|
+
재시도로 같은 배치가 두 번 도착해도 서버가 시퀀스로 걸러 **한 번만 적재한다.** 걸러지는 것은
|
|
333
|
+
producer 안의 재시도뿐이다 - 앱이 ``send`` 를 다시 부르면 새 레코드로 적재된다.
|
|
325
334
|
``maxInFlight`` 는 5 이하여야 한다.
|
|
326
335
|
|
|
327
336
|
이 메서드로 정하지 않으면 ``acks=0`` 이거나 ``maxInFlight`` 가 5 를 넘을 때 멱등이 저절로 꺼진다.
|
|
@@ -422,10 +431,10 @@ class ILProducerConfig:
|
|
|
422
431
|
return self._lingerMs
|
|
423
432
|
|
|
424
433
|
def get_batch_size(self) -> int:
|
|
425
|
-
"""파티션 배치 하나의 크기 상한(바이트). 기본
|
|
434
|
+
"""파티션 배치 하나의 크기 상한(바이트). 기본 262144.
|
|
426
435
|
|
|
427
436
|
Returns:
|
|
428
|
-
int: 파티션 배치 하나의 크기 상한(바이트). 기본
|
|
437
|
+
int: 파티션 배치 하나의 크기 상한(바이트). 기본 262144.
|
|
429
438
|
"""
|
|
430
439
|
return self._batchSize
|
|
431
440
|
|
|
@@ -742,31 +751,75 @@ class ILRecordMetadata:
|
|
|
742
751
|
return "ILRecordMetadata(%s[%d]@%d)" % (self.topic, self.partition, self.offset)
|
|
743
752
|
|
|
744
753
|
|
|
754
|
+
class _BatchResult:
|
|
755
|
+
"""파티션 배치 하나의 결과(0.7.2). 같은 배치의 레코드 future 들이 함께 가리킨다 - 완결은 배치당 한 번이다."""
|
|
756
|
+
|
|
757
|
+
__slots__ = ("topic", "partition", "event", "base_offset", "timestamp", "error")
|
|
758
|
+
|
|
759
|
+
def __init__(self, topic: str | None, partition: int) -> None:
|
|
760
|
+
"""배치 결과를 만든다.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
topic (str | None): 토픽 이름.
|
|
764
|
+
partition (int): 파티션 번호.
|
|
765
|
+
"""
|
|
766
|
+
self.topic = topic
|
|
767
|
+
self.partition = partition
|
|
768
|
+
self.event = threading.Event()
|
|
769
|
+
self.base_offset = -1
|
|
770
|
+
self.timestamp = 0
|
|
771
|
+
self.error: Exception | None = None
|
|
772
|
+
|
|
773
|
+
def complete(self, base_offset: int, timestamp: int) -> None:
|
|
774
|
+
"""성공으로 정한다.
|
|
775
|
+
|
|
776
|
+
Args:
|
|
777
|
+
base_offset (int): 배치 첫 레코드의 오프셋(-1 = 모름).
|
|
778
|
+
timestamp (int): 적재 시각.
|
|
779
|
+
"""
|
|
780
|
+
self.base_offset = base_offset
|
|
781
|
+
self.timestamp = timestamp
|
|
782
|
+
self.event.set()
|
|
783
|
+
|
|
784
|
+
def fail(self, error: Exception) -> None:
|
|
785
|
+
"""실패로 정한다.
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
error (Exception): 실패 원인.
|
|
789
|
+
"""
|
|
790
|
+
self.error = error
|
|
791
|
+
self.event.set()
|
|
792
|
+
|
|
793
|
+
|
|
745
794
|
class ILPublishFuture:
|
|
746
795
|
"""발행 완결을 기다리는 핸들. :meth:`get` 이 :class:`ILRecordMetadata` 를 돌려준다.
|
|
747
796
|
|
|
748
797
|
**동기 발행은 ``send(rec).get()``** 으로 쓴다.
|
|
749
798
|
"""
|
|
750
799
|
|
|
751
|
-
def __init__(self, timeout_ms: int) -> None:
|
|
800
|
+
def __init__(self, timeout_ms: int, _result: "_BatchResult | None" = None, _index: int = 0) -> None:
|
|
752
801
|
"""**응용이 직접 만들지 않는다** - :meth:`ILTopicProducer.send` 가 돌려준다.
|
|
753
802
|
|
|
754
803
|
Args:
|
|
755
804
|
timeout_ms (int): :meth:`get` 에 인자를 주지 않았을 때 쓸 기본 대기
|
|
756
805
|
시간(밀리초). producer 의 ``deliveryTimeoutMs`` 가 들어온다.
|
|
806
|
+
_result (_BatchResult | None): 내부용 - 이 레코드가 든 파티션 배치의 결과(배치의 레코드들이 함께 가리킨다).
|
|
807
|
+
_index (int): 내부용 - 배치 안 순번. 좌표는 배치 첫 오프셋 + 순번이다.
|
|
757
808
|
"""
|
|
758
|
-
|
|
809
|
+
# 0.7.2: 완결은 배치 결과 하나로 한다(레코드마다 Event 를 만들지 않는다). 좌표는 get() 때 만든다
|
|
810
|
+
self._result = _result if _result is not None else _BatchResult(None, -1)
|
|
811
|
+
self._index = _index
|
|
759
812
|
self._meta: ILRecordMetadata | None = None
|
|
760
|
-
self._error: Exception | None = None
|
|
761
813
|
self._timeout_ms = timeout_ms
|
|
762
814
|
|
|
763
815
|
def _complete(self, meta: ILRecordMetadata) -> None:
|
|
816
|
+
# 배치 없이 만든 future 용(호환)
|
|
764
817
|
self._meta = meta
|
|
765
|
-
self.
|
|
818
|
+
self._result.event.set()
|
|
766
819
|
|
|
767
820
|
def _fail(self, error: Exception) -> None:
|
|
768
|
-
self.
|
|
769
|
-
self.
|
|
821
|
+
self._result.error = error
|
|
822
|
+
self._result.event.set()
|
|
770
823
|
|
|
771
824
|
def get(self, timeout_ms: int | None = None) -> ILRecordMetadata:
|
|
772
825
|
"""발행이 끝날 때까지 기다렸다가 좌표를 돌려준다.
|
|
@@ -788,12 +841,18 @@ class ILPublishFuture:
|
|
|
788
841
|
완결은 producer 의 I/O 스레드가 한다(0.7.0). ``lingerMs`` 가 0 이어도 ``send()``
|
|
789
842
|
가 돌아온 시점에 완결되어 있다는 보장은 없다 - 결과가 필요하면 이 메서드로 기다린다.
|
|
790
843
|
"""
|
|
844
|
+
r = self._result
|
|
791
845
|
wait = (timeout_ms if timeout_ms is not None else self._timeout_ms) / 1000.0
|
|
792
|
-
if not
|
|
846
|
+
if not r.event.wait(wait if wait > 0 else None):
|
|
793
847
|
raise ILException(TimeoutError("publish timed out"))
|
|
794
|
-
if
|
|
795
|
-
raise
|
|
796
|
-
|
|
848
|
+
if r.error is not None:
|
|
849
|
+
raise r.error
|
|
850
|
+
m = self._meta
|
|
851
|
+
if m is None:
|
|
852
|
+
m = ILRecordMetadata(r.topic, r.partition, r.base_offset + self._index if r.base_offset >= 0 else -1,
|
|
853
|
+
r.timestamp)
|
|
854
|
+
self._meta = m
|
|
855
|
+
return m
|
|
797
856
|
|
|
798
857
|
def is_done(self) -> bool:
|
|
799
858
|
"""완결(성공이든 실패든)되었는지.
|
|
@@ -801,7 +860,7 @@ class ILPublishFuture:
|
|
|
801
860
|
Returns:
|
|
802
861
|
bool: 결과가 정해졌으면 True. True 여도 :meth:`get` 이 예외를 올릴 수 있다.
|
|
803
862
|
"""
|
|
804
|
-
return self.
|
|
863
|
+
return self._result.event.is_set()
|
|
805
864
|
|
|
806
865
|
def cancel(self, may_interrupt_if_running: bool = False) -> bool:
|
|
807
866
|
"""발행은 취소할 수 없다. **항상 False**.
|
|
@@ -958,18 +1017,19 @@ def _as_il(exc: BaseException) -> Exception:
|
|
|
958
1017
|
class _Batch:
|
|
959
1018
|
"""파티션 배치 1개 = 와이어의 PartitionBatch 1개. ``buf`` 의 앞 52바이트가 배치 헤더다."""
|
|
960
1019
|
|
|
961
|
-
__slots__ = ("partition", "created", "batch_no", "buf", "count", "
|
|
1020
|
+
__slots__ = ("partition", "created", "batch_no", "buf", "count", "result", "callbacks",
|
|
962
1021
|
"closed", "done", "base_seq", "header_done", "header_pid", "retry_count",
|
|
963
1022
|
"retry_at", "unknown", "size_drops", "solo")
|
|
964
1023
|
|
|
965
|
-
def __init__(self, partition: int, created: float, batch_no: int) -> None:
|
|
1024
|
+
def __init__(self, partition: int, created: float, batch_no: int, topic: str = "") -> None:
|
|
966
1025
|
self.partition = partition
|
|
967
1026
|
self.created = created
|
|
968
1027
|
#: 만든 순서. 파티션 안에서 보내야 하는 순서 = 이 순서
|
|
969
1028
|
self.batch_no = batch_no
|
|
970
1029
|
self.buf = bytearray(_BATCH_HEADER)
|
|
971
1030
|
self.count = 0
|
|
972
|
-
|
|
1031
|
+
#: 이 배치 레코드들의 결과(0.7.2, 배치당 하나). 레코드 future 는 이것과 배치 안 순번만 든다
|
|
1032
|
+
self.result = _BatchResult(topic, partition)
|
|
973
1033
|
self.callbacks: list | None = None
|
|
974
1034
|
#: 더 붙이지 않는다(batchSize 에 닿음, drain 이 가져감, 되돌림)
|
|
975
1035
|
self.closed = False
|
|
@@ -1032,8 +1092,9 @@ class ILTopicProducer:
|
|
|
1032
1092
|
- **순서**: 한 파티션 안에서는 ``send`` 순서대로 적재된다. 파티션 사이의 순서는 보장하지 않는다.
|
|
1033
1093
|
비멱등에서 FLUSH TOPIC 과 겹친 배치를 다시 보낼 때는 이미 나가 있던 같은 파티션의 뒤 배치가
|
|
1034
1094
|
먼저 적재될 수 있다.
|
|
1035
|
-
- **전달**: ``acks=1`` + 재시도 = at-least-once.
|
|
1036
|
-
제거되어
|
|
1095
|
+
- **전달**: ``acks=1`` + 재시도 = at-least-once. 멱등(기본 켜짐)이면 producer 안의 재전송이 엔진에서
|
|
1096
|
+
중복 제거되어 중복 적재가 없다(적재하지 못한 배치는 future 가 실패로 알린다). 앱이 ``send`` 를 다시
|
|
1097
|
+
부르면 새 레코드라 멱등이어도 걸러지지 않는다. ``acks=0`` = at-most-once.
|
|
1037
1098
|
- **재시도**: 연결 단절, 비멱등의 FLUSHED / epoch 불일치(적재되지 않은 것이 확정된 거절), 멱등의
|
|
1038
1099
|
순서 오류다. 그 밖의 거절(토픽 없음, 너무 큼 등)은 재시도 없이 그 배치의 future 가 실패한다.
|
|
1039
1100
|
PID 가 무효가 되면 PID 를 다시 받고 seq 를 0 부터 다시 매긴다.
|
|
@@ -1059,7 +1120,8 @@ class ILTopicProducer:
|
|
|
1059
1120
|
port (int | None): 포트. ``host`` 에 ``:포트`` 가 붙어 있으면 생략한다.
|
|
1060
1121
|
client_name (str): 큐 관리자에 등록될 클라이언트 이름. 기본 ``"PRODUCER"``.
|
|
1061
1122
|
topic (str): 발행할 토픽 이름. **필수.** 이 producer 는 이 토픽에만 발행한다.
|
|
1062
|
-
config (ILProducerConfig | None): 발행 설정. 기본 None 이면 전 항목 기본값.
|
|
1123
|
+
config (ILProducerConfig | None): 발행 설정. 기본 None 이면 전 항목 기본값. 생성 때 복사한다 -
|
|
1124
|
+
이후 넘긴 객체를 바꿔도 이 producer 에는 반영되지 않는다.
|
|
1063
1125
|
|
|
1064
1126
|
Raises:
|
|
1065
1127
|
TypeError: ``topic`` 이 토픽 이름(str)이 아닐 때 - 0.6.x 의
|
|
@@ -1081,7 +1143,8 @@ class ILTopicProducer:
|
|
|
1081
1143
|
" (got %s) - since 0.7.0 a producer publishes to one topic" % type(topic).__name__)
|
|
1082
1144
|
if not topic:
|
|
1083
1145
|
raise ValueError("topic must not be empty - a producer publishes to one topic")
|
|
1084
|
-
|
|
1146
|
+
# 0.7.2: 설정을 생성 때 복사한다(Kafka·자바와 같다) - 넘긴 객체를 나중에 바꿔도 이 producer 에는 반영되지 않는다
|
|
1147
|
+
self.config = copy.copy(config) if config is not None else ILProducerConfig()
|
|
1085
1148
|
self.config.validate()
|
|
1086
1149
|
cfg = self.config
|
|
1087
1150
|
self.topic = topic
|
|
@@ -1098,6 +1161,13 @@ class ILTopicProducer:
|
|
|
1098
1161
|
self._backoff = cfg.get_retry_backoff_ms() / 1000.0
|
|
1099
1162
|
self._request_timeout = cfg.get_request_timeout_ms() / 1000.0
|
|
1100
1163
|
self._delivery_timeout = cfg.get_delivery_timeout_ms() / 1000.0
|
|
1164
|
+
# 0.7.2: send() 가 레코드마다 부르던 설정 게터를 생성 때 한 번 읽는다
|
|
1165
|
+
self._delivery_timeout_ms = cfg.get_delivery_timeout_ms()
|
|
1166
|
+
self._max_request_size = cfg.get_max_request_size()
|
|
1167
|
+
self._max_frame_length = cfg.get_max_frame_length()
|
|
1168
|
+
self._buffer_memory = cfg.get_buffer_memory()
|
|
1169
|
+
self._max_block = cfg.get_max_block_ms() / 1000.0
|
|
1170
|
+
self._batch_size = cfg.get_batch_size()
|
|
1101
1171
|
#: 봉투 본문(IL 헤더 뒤) 상한 = min(maxRequestSize, maxFrameLength - 25)
|
|
1102
1172
|
self._max_env_content = min(cfg.get_max_request_size(),
|
|
1103
1173
|
cfg.get_max_frame_length() - ILC.SZ_IL_HEADER)
|
|
@@ -1134,6 +1204,10 @@ class ILTopicProducer:
|
|
|
1134
1204
|
self._used = 0
|
|
1135
1205
|
self._sticky = -1
|
|
1136
1206
|
self._sticky_bytes = 0
|
|
1207
|
+
#: 키 -> 파티션 캐시(0.7.2). 파티션 수가 바뀌면 비운다
|
|
1208
|
+
self._key_parts: dict = {}
|
|
1209
|
+
#: 깨울 신호를 이미 보냈고 I/O 스레드가 아직 비우지 않았다(0.7.2 - 신호를 합친다)
|
|
1210
|
+
self._wake_pending = False
|
|
1137
1211
|
self._pid = -1
|
|
1138
1212
|
#: drain 이 큐에서 꺼낸 배치를 아직 in-flight 에 올리지(acks=0 이면 완결하지) 않은 동안 1 - flush 가 먼저 돌아가지 않게
|
|
1139
1213
|
self._draining = 0
|
|
@@ -1288,6 +1362,8 @@ class ILTopicProducer:
|
|
|
1288
1362
|
self._parts = parts
|
|
1289
1363
|
if self._sticky >= n:
|
|
1290
1364
|
self._sticky = -1
|
|
1365
|
+
if n != self._partition_count:
|
|
1366
|
+
self._key_parts.clear()
|
|
1291
1367
|
self._partition_count = n
|
|
1292
1368
|
self._cv.notify_all()
|
|
1293
1369
|
if len(self._next_seq) != n:
|
|
@@ -1378,22 +1454,21 @@ class ILTopicProducer:
|
|
|
1378
1454
|
rec_size = _RECORD_OVERHEAD + kl + pl + len(value)
|
|
1379
1455
|
# 이 레코드 혼자 실린 봉투의 본문 크기
|
|
1380
1456
|
alone = _ENVELOPE_FIXED + len(self._topic_bytes) + _BATCH_HEADER + rec_size
|
|
1381
|
-
if alone >
|
|
1457
|
+
if alone > self._max_request_size:
|
|
1382
1458
|
raise ILOperationException("MIMQE_MSG_SIZE_OVER (record too large: %d > maxRequestSize %d - envelope carrying"
|
|
1383
|
-
" this record alone)" % (alone,
|
|
1459
|
+
" this record alone)" % (alone, self._max_request_size))
|
|
1384
1460
|
# 엔진은 Runtime@MaxFrameLength(기본 128MiB) 를 넘는 프레임을 사유 없이 끊는다 - 보내기 전에 막는다
|
|
1385
|
-
if alone + ILC.SZ_IL_HEADER >
|
|
1461
|
+
if alone + ILC.SZ_IL_HEADER > self._max_frame_length:
|
|
1386
1462
|
raise ILOperationException(
|
|
1387
1463
|
"MIMQE_MSG_SIZE_OVER (record too large: frame %d > maxFrameLength %d - the engine drops the connection"
|
|
1388
1464
|
" over its Runtime@MaxFrameLength; raise both if the server allows more)"
|
|
1389
|
-
% (alone + ILC.SZ_IL_HEADER,
|
|
1465
|
+
% (alone + ILC.SZ_IL_HEADER, self._max_frame_length))
|
|
1390
1466
|
rec = b"".join((_I32.pack(kl if key else -1), key or b"",
|
|
1391
1467
|
_I32.pack(pl if props else -1), props or b"",
|
|
1392
1468
|
_I32.pack(len(value)), value))
|
|
1393
|
-
future = ILPublishFuture(cfg.get_delivery_timeout_ms())
|
|
1394
1469
|
with self._cv:
|
|
1395
|
-
# maxBlockMs 는 버퍼 대기의 상한이다(Kafka max.block.ms)
|
|
1396
|
-
deadline =
|
|
1470
|
+
# maxBlockMs 는 버퍼 대기의 상한이다(Kafka max.block.ms). 기한은 기다려야 할 때만 잡는다
|
|
1471
|
+
deadline = None
|
|
1397
1472
|
partition = self._choose_partition(record.partition, key)
|
|
1398
1473
|
while True:
|
|
1399
1474
|
if self._fatal is not None:
|
|
@@ -1415,16 +1490,18 @@ class ILTopicProducer:
|
|
|
1415
1490
|
fresh = (b is None or b.closed or len(b.buf) + rec_size > self._batch_limit
|
|
1416
1491
|
or (seg > 0 and len(b.buf) + rec_size + (b.count + 1) * self._v3_per_record > seg))
|
|
1417
1492
|
need = rec_size + (_BATCH_HEADER if fresh else 0)
|
|
1418
|
-
if need >
|
|
1493
|
+
if need > self._buffer_memory:
|
|
1419
1494
|
raise ILOperationException("MIMQE_MSG_SIZE_OVER (record too large: needs %d bytes of buffer"
|
|
1420
|
-
" > bufferMemory %d)" % (need,
|
|
1421
|
-
if self._used + need >
|
|
1495
|
+
" > bufferMemory %d)" % (need, self._buffer_memory))
|
|
1496
|
+
if self._used + need > self._buffer_memory:
|
|
1422
1497
|
# 버퍼 확보(가득이면 maxBlockMs 대기). 기다리는 사이 배치가 나가거나 파티션 수가 바뀔 수 있어 처음부터 다시 본다
|
|
1498
|
+
if deadline is None:
|
|
1499
|
+
deadline = time.monotonic() + self._max_block
|
|
1423
1500
|
remain = deadline - time.monotonic()
|
|
1424
1501
|
if remain <= 0:
|
|
1425
1502
|
# 부하 문제다(Kafka BufferExhaustedException) - 큐 가득과 같은 분류(재시도 가능)로 올린다
|
|
1426
1503
|
raise ILOperationException("MIMQE_QUEUE_FULL (buffer memory full %d/%d - blocked over maxBlockMs)"
|
|
1427
|
-
% (self._used,
|
|
1504
|
+
% (self._used, self._buffer_memory))
|
|
1428
1505
|
self._cv.wait(remain)
|
|
1429
1506
|
continue
|
|
1430
1507
|
wake = False
|
|
@@ -1432,14 +1509,14 @@ class ILTopicProducer:
|
|
|
1432
1509
|
if b is not None:
|
|
1433
1510
|
b.closed = True
|
|
1434
1511
|
self._batch_counter += 1
|
|
1435
|
-
b = _Batch(partition, time.monotonic(), self._batch_counter)
|
|
1512
|
+
b = _Batch(partition, time.monotonic(), self._batch_counter, self.topic)
|
|
1436
1513
|
q.append(b)
|
|
1437
1514
|
self._queued += 1
|
|
1438
1515
|
self._used += _BATCH_HEADER
|
|
1439
1516
|
wake = True
|
|
1440
1517
|
b.buf += rec
|
|
1441
1518
|
self._used += rec_size
|
|
1442
|
-
b.
|
|
1519
|
+
future = ILPublishFuture(self._delivery_timeout_ms, b.result, b.count)
|
|
1443
1520
|
if callback is not None:
|
|
1444
1521
|
if b.callbacks is None:
|
|
1445
1522
|
b.callbacks = [None] * b.count
|
|
@@ -1468,7 +1545,15 @@ class ILTopicProducer:
|
|
|
1468
1545
|
% (explicit, n - 1))
|
|
1469
1546
|
return explicit
|
|
1470
1547
|
if key:
|
|
1471
|
-
|
|
1548
|
+
# 0.7.2: 같은 키는 같은 파티션이다 - 파티션 수가 그대로인 동안 결과를 캐시한다(순수 파이썬 murmur2 가 레코드당 수 us)
|
|
1549
|
+
p = self._key_parts.get(key)
|
|
1550
|
+
if p is None:
|
|
1551
|
+
p = partition_for_key(key, n)
|
|
1552
|
+
if len(key) <= _KEY_CACHE_MAX_KEY:
|
|
1553
|
+
if len(self._key_parts) >= _KEY_CACHE_MAX:
|
|
1554
|
+
self._key_parts.clear()
|
|
1555
|
+
self._key_parts[key] = p
|
|
1556
|
+
return p
|
|
1472
1557
|
if self._sticky < 0 or self._sticky >= n:
|
|
1473
1558
|
self._sticky = random.randrange(n)
|
|
1474
1559
|
self._sticky_bytes = 0
|
|
@@ -1477,7 +1562,7 @@ class ILTopicProducer:
|
|
|
1477
1562
|
def _sticky_advance(self, nbytes: int) -> None:
|
|
1478
1563
|
"""키 없는 레코드를 붙인 만큼 센다. ``batchSize`` 를 채우면 다음 파티션으로 넘긴다(Kafka 3.3+ 균일 스티키)."""
|
|
1479
1564
|
self._sticky_bytes += nbytes
|
|
1480
|
-
if self._sticky_bytes >= self.
|
|
1565
|
+
if self._sticky_bytes >= self._batch_size:
|
|
1481
1566
|
self._sticky = (self._sticky + 1) % self._partition_count
|
|
1482
1567
|
self._sticky_bytes = 0
|
|
1483
1568
|
|
|
@@ -1596,10 +1681,15 @@ class ILTopicProducer:
|
|
|
1596
1681
|
|
|
1597
1682
|
# ── I/O 스레드 ───────────────────────────────────────────────────────
|
|
1598
1683
|
def _wakeup(self) -> None:
|
|
1684
|
+
# 0.7.2: 깨울 신호가 이미 가 있으면(I/O 스레드가 아직 비우지 않았으면) 다시 쓰지 않는다 - 배치를 열고 닫을 때마다 쓰던
|
|
1685
|
+
# 시스템 호출을 합친다. I/O 스레드는 신호를 비운 뒤에 이 표시를 내리고, 그다음에 큐를 본다
|
|
1686
|
+
if self._wake_pending:
|
|
1687
|
+
return
|
|
1688
|
+
self._wake_pending = True
|
|
1599
1689
|
try:
|
|
1600
1690
|
self._wake_w.send(b"\x00")
|
|
1601
1691
|
except OSError:
|
|
1602
|
-
|
|
1692
|
+
self._wake_pending = False
|
|
1603
1693
|
|
|
1604
1694
|
def _drain_wake(self) -> None:
|
|
1605
1695
|
try:
|
|
@@ -1607,6 +1697,8 @@ class ILTopicProducer:
|
|
|
1607
1697
|
pass
|
|
1608
1698
|
except OSError:
|
|
1609
1699
|
pass
|
|
1700
|
+
# 비운 뒤에 내린다. 먼저 내리면 비우는 사이 온 신호를 먹고도 표시가 올라간 채 남아 다음 깨우기가 막힐 수 있다
|
|
1701
|
+
self._wake_pending = False
|
|
1610
1702
|
|
|
1611
1703
|
def _wake_waiters(self) -> None:
|
|
1612
1704
|
with self._cv:
|
|
@@ -1965,45 +2057,56 @@ class ILTopicProducer:
|
|
|
1965
2057
|
raise ConnectionError(results[0][4] or "MIMQE_INVALID_PID")
|
|
1966
2058
|
now = time.monotonic()
|
|
1967
2059
|
again = []
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
if
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2060
|
+
# 0.7.2: 이 ACK 에서 완결한 배치들의 버퍼는 아래 잠금 한 번에 반납한다(배치마다 잠금을 잡지 않는다)
|
|
2061
|
+
freed = 0
|
|
2062
|
+
try:
|
|
2063
|
+
for b, (code, off, ts, exp, reason) in zip(env.batches, results):
|
|
2064
|
+
# seq 틈: 이 배치의 어느 시도도 적재됐을 수 없는데(결과를 모른 적이 없다) 엔진이 중복이라 한다 = 같은 파티션의
|
|
2065
|
+
# 뒤 배치가 먼저 기준선으로 적재돼(두 봉투 사이 리더 교체 등) 이 번호가 틈으로 남았다. 성공으로 올리면 조용한 유실이다.
|
|
2066
|
+
if code == ILC.MIMQ_TOPIC_PUBLISH_DUPLICATE and self._idem and b.base_seq >= 0 and not b.unknown:
|
|
2067
|
+
freed += self._complete_batch(b, -1, 0, ILOperationException(
|
|
2068
|
+
"MIMQE_OUT_OF_ORDER_SEQUENCE (sequence gap: partition %d seq %d..%d was never stored but the engine"
|
|
2069
|
+
" answered duplicate - a later batch of this partition was stored first)"
|
|
2070
|
+
% (b.partition, b.base_seq, b.base_seq + b.count - 1)), release=False)
|
|
2071
|
+
continue
|
|
2072
|
+
if code == ILC.MIMQ_SUCCESSFUL_TOPIC_PUBLISH or code == ILC.MIMQ_TOPIC_PUBLISH_DUPLICATE:
|
|
2073
|
+
# 중복 응답은 원 좌표로 성공한다. 엔진 링 밖이면 baseOffset 이 -1 이다.
|
|
2074
|
+
self._pid_resets = 0
|
|
2075
|
+
if self._idem and b.base_seq >= 0 and b.partition < len(self._acked_seq):
|
|
2076
|
+
last = b.base_seq + b.count - 1
|
|
2077
|
+
if last > self._acked_seq[b.partition]:
|
|
2078
|
+
self._acked_seq[b.partition] = last
|
|
2079
|
+
freed += self._complete_batch(b, off, ts, None, release=False)
|
|
2080
|
+
continue
|
|
2081
|
+
# epoch 가 바뀌었거나(FLUSH / 재생성), 토픽이 비워지는 중이거나, 세그먼트가 줄었다 - 메타데이터를 다시 받는다
|
|
2082
|
+
if code in (ILC.MIMQ_TOPIC_PUBLISH_EPOCH_MISMATCH, ILC.MIMQ_TOPIC_PUBLISH_FLUSHED,
|
|
2083
|
+
ILC.MIMQ_TOPIC_PUBLISH_TOO_LARGE):
|
|
2084
|
+
self._meta_stale = True
|
|
2085
|
+
expected = exp if code == ILC.MIMQ_OUT_OF_ORDER_SEQUENCE else -1
|
|
2086
|
+
if self._idem and b.base_seq >= 0:
|
|
2087
|
+
# 배치별 거절 = 이번 시도는 적재되지 않았다(엔진 계약). 번호를 돌려받고 재번호를 건다.
|
|
2088
|
+
# 단 앞선 시도가 답 없이 끝난 배치(unknown)는 그때 적재됐을 수 있다 - 순서 오류가 아니면 번호를
|
|
2089
|
+
# 돌려받지 않는다. 돌려받아 다음 새 배치에 주면 그 배치가 엔진에서 중복 응답으로 사라진다(유실).
|
|
2090
|
+
if expected >= 0 or not b.unknown:
|
|
2091
|
+
self._note_rejected(b.partition, b.base_seq, expected)
|
|
2092
|
+
b.base_seq = -1
|
|
2093
|
+
b.header_done = False
|
|
2094
|
+
if self._can_retry(b, code, expected >= 0, now):
|
|
2095
|
+
b.retry_count += 1
|
|
2096
|
+
b.retry_at = now + self._backoff
|
|
2097
|
+
again.append(b)
|
|
2098
|
+
else:
|
|
2099
|
+
freed += self._complete_batch(b, -1, 0, _batch_error(code, reason), release=False)
|
|
2100
|
+
except BaseException:
|
|
2101
|
+
# 도중에 끊겨도 이미 완결한 배치의 버퍼는 돌려준다
|
|
2102
|
+
if freed:
|
|
2103
|
+
with self._cv:
|
|
2104
|
+
self._used -= freed
|
|
2105
|
+
self._cv.notify_all()
|
|
2106
|
+
raise
|
|
2005
2107
|
gone: list = []
|
|
2006
2108
|
with self._cv:
|
|
2109
|
+
self._used -= freed
|
|
2007
2110
|
# 배치는 되돌린 것을 큐에 넣은 **뒤에** in-flight 에서 뺀다. 먼저 빼면 그 사이 flush() 가 비었다고 보고 돌아간다.
|
|
2008
2111
|
if again:
|
|
2009
2112
|
gone = self._requeue_batches(again)
|
|
@@ -2082,30 +2185,40 @@ class ILTopicProducer:
|
|
|
2082
2185
|
self._queued += 1
|
|
2083
2186
|
return gone
|
|
2084
2187
|
|
|
2085
|
-
def _complete_batch(self, b: _Batch, base_offset: int, timestamp: int, err: Exception | None
|
|
2188
|
+
def _complete_batch(self, b: _Batch, base_offset: int, timestamp: int, err: Exception | None,
|
|
2189
|
+
release: bool = True) -> int:
|
|
2190
|
+
"""배치 하나를 완결한다. 레코드 future 들은 배치 결과 하나를 함께 가리키므로 결과를 한 번 정하면 모두 풀린다(0.7.2).
|
|
2191
|
+
|
|
2192
|
+
``release`` 가 False 면 버퍼 반납(``self._used``)과 대기자 깨우기를 부른 쪽이 한꺼번에 한다 -
|
|
2193
|
+
ACK 하나에 든 배치들을 잠금 한 번에 반납하려는 것이다. 반납할 바이트 수를 돌려준다.
|
|
2194
|
+
"""
|
|
2086
2195
|
if b.done:
|
|
2087
|
-
return
|
|
2196
|
+
return 0
|
|
2088
2197
|
b.done = True
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
self._cv
|
|
2092
|
-
|
|
2093
|
-
|
|
2198
|
+
freed = len(b.buf)
|
|
2199
|
+
if release:
|
|
2200
|
+
with self._cv:
|
|
2201
|
+
self._used -= freed
|
|
2202
|
+
self._cv.notify_all()
|
|
2203
|
+
r = b.result
|
|
2204
|
+
if err is None:
|
|
2205
|
+
r.complete(base_offset, timestamp)
|
|
2206
|
+
else:
|
|
2207
|
+
r.fail(err)
|
|
2208
|
+
callbacks = b.callbacks
|
|
2094
2209
|
b.callbacks = None
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
fut._fail(err)
|
|
2103
|
-
cb = callbacks[i] if callbacks is not None else None
|
|
2104
|
-
if cb is not None:
|
|
2210
|
+
if callbacks:
|
|
2211
|
+
topic = self.topic
|
|
2212
|
+
for i, cb in enumerate(callbacks):
|
|
2213
|
+
if cb is None:
|
|
2214
|
+
continue
|
|
2215
|
+
meta = (ILRecordMetadata(topic, b.partition, base_offset + i if base_offset >= 0 else -1, timestamp)
|
|
2216
|
+
if err is None else None)
|
|
2105
2217
|
try:
|
|
2106
2218
|
cb(meta, err)
|
|
2107
2219
|
except Exception: # noqa: BLE001 - 콜백 예외는 무시한다(계약)
|
|
2108
2220
|
pass
|
|
2221
|
+
return freed
|
|
2109
2222
|
|
|
2110
2223
|
def _fail_batches(self, batches, err: Exception | None) -> None:
|
|
2111
2224
|
"""파티션이 없어진 배치 등을 실패시킨다(self._cv 밖에서 부른다)."""
|
|
@@ -1139,7 +1139,7 @@ class ILQmgr:
|
|
|
1139
1139
|
Args:
|
|
1140
1140
|
topic (str): 발행할 토픽 이름. 이 producer 는 이 토픽에만 발행한다.
|
|
1141
1141
|
config (ILProducerConfig | None): 발행 설정. 기본 None 이면 전 항목
|
|
1142
|
-
기본값(acks=1 / linger 0 / batch
|
|
1142
|
+
기본값(acks=1 / linger 0 / batch 256KB / maxInFlight 5 / 멱등 켜짐).
|
|
1143
1143
|
|
|
1144
1144
|
Returns:
|
|
1145
1145
|
ILTopicProducer: 발행자. 다 쓰면 ``close()`` 로 닫는다.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jetstream-api
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Python client API for iLink M.O.M. (Message Oriented Middleware)
|
|
5
5
|
Author: snowjeans
|
|
6
6
|
License: Boost Software License - Version 1.0 - August 17th, 2003
|
|
@@ -54,6 +54,17 @@ Python으로 작성된 ILink 클라이언트 API입니다.
|
|
|
54
54
|
## 설치 방법
|
|
55
55
|
pip install jetstream-api
|
|
56
56
|
|
|
57
|
+
## 0.7.2 변경 - batchSize 기본값 262144 로 되돌림, 발행 경로 CPU 절감 (자바 v2.3.2 와 같음)
|
|
58
|
+
|
|
59
|
+
- `batchSize` 기본값을 **262144** 로 되돌렸습니다. 0.7.1 의 16384 에서 파이썬 클라는 처리량이 24~35% 낮았습니다(GIL 이
|
|
60
|
+
천장이라 배치마다 드는 비용이 그대로 처리량에서 빠집니다). 멱등 기본 켜짐은 그대로입니다.
|
|
61
|
+
- 발행 경로 CPU 를 줄였습니다(API 는 그대로입니다). 레코드 future 가 배치 결과 하나를 함께 가리키고(완결은 배치당 한 번),
|
|
62
|
+
키 -> 파티션 결과를 캐시하고, `send()` 가 설정값을 레코드마다 다시 읽지 않고, I/O 스레드를 깨우는 신호를 합치고,
|
|
63
|
+
ACK 하나의 배치 완결을 잠금 한 번에 묶습니다.
|
|
64
|
+
- producer 는 만들 때 설정 객체를 **복사**합니다(자바 v2.3.2 도 같고, Kafka 와 같습니다). 만든 뒤 넘긴 객체를 바꿔도 그
|
|
65
|
+
producer 에는 반영되지 않습니다 - 설정을 바꾸려면 producer 를 새로 만드십시오.
|
|
66
|
+
- 문서: 멱등이 거르는 것은 producer 안의 재전송뿐입니다 - 앱이 `send` 를 다시 부르면 새 레코드로 적재됩니다.
|
|
67
|
+
|
|
57
68
|
## 0.7.1 변경 - producer 기본값: 멱등 켜짐, batchSize 16384 (자바 v2.3.1 과 같음)
|
|
58
69
|
|
|
59
70
|
- `enableIdempotence` 기본값이 **켜짐**입니다. 멱등을 직접 정하지 않았으면 `acks(0)` 이나 `maxInFlight` 가 5 를 넘을 때
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|