jetstream-api 0.19.3__tar.gz → 0.19.5__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.
Files changed (29) hide show
  1. {jetstream_api-0.19.3/jetstream_api.egg-info → jetstream_api-0.19.5}/PKG-INFO +23 -2
  2. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/README.md +22 -1
  3. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/__init__.py +1 -1
  4. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/producer.py +64 -24
  5. {jetstream_api-0.19.3 → jetstream_api-0.19.5/jetstream_api.egg-info}/PKG-INFO +23 -2
  6. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/pyproject.toml +1 -1
  7. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/LICENSE.txt +0 -0
  8. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/_codec.py +0 -0
  9. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/_consumer.py +0 -0
  10. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/_groupwire.py +0 -0
  11. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/admin.py +0 -0
  12. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/admintopic.py +0 -0
  13. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/common.py +0 -0
  14. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/connectivity.py +0 -0
  15. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/exception.py +0 -0
  16. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/msg.py +0 -0
  17. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/partitioner.py +0 -0
  18. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/pattern.py +0 -0
  19. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/properties.py +0 -0
  20. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/py.typed +0 -0
  21. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/qmgr.py +0 -0
  22. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/topic.py +0 -0
  23. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/ilink/util.py +0 -0
  24. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/jetstream_api.egg-info/SOURCES.txt +0 -0
  25. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/jetstream_api.egg-info/dependency_links.txt +0 -0
  26. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/jetstream_api.egg-info/requires.txt +0 -0
  27. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/jetstream_api.egg-info/top_level.txt +0 -0
  28. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/setup.cfg +0 -0
  29. {jetstream_api-0.19.3 → jetstream_api-0.19.5}/tests/test_example.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetstream-api
3
- Version: 0.19.3
3
+ Version: 0.19.5
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
@@ -60,6 +60,27 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
60
60
  ## 설치 방법
61
61
  pip install jetstream-api
62
62
 
63
+ ## 0.19.5 변경 - 멱등 producer 도 FLUSH TOPIC / epoch 변경을 넘긴다 (자바 v2.15.5 와 같음, 짝 엔진 7.0.1.3512 이상)
64
+
65
+ > 0.19.4 는 시험 저장소(test.pypi)에만 올렸고 배포하지 않았습니다. 0.19.4 는 앞선 시도의 결과를 모르는 배치가 이 거절을
66
+ > 받으면 `commit in doubt` 로 올렸는데, FLUSH 순간 복제 확인을 기다리던 배치가 늘 그 경우라(엔진이 "결과 모름" 으로 답합니다)
67
+ > FLUSH 때마다 앱에 드러났습니다(회귀 FLUSHOLDPROD-1). 아래가 고친 동작입니다.
68
+
69
+ - 멱등 producer(기본)가 `MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH` 로 거절된 배치를 **앱에 올리지 않고**
70
+ 메타데이터를 다시 받아 다시 보냅니다. 0.19.3 까지는 멱등이면 실패로 올렸습니다(0.7.1 절) - FLUSH 로 새로 만든 토픽은
71
+ 처음 받은 번호를 기준선으로 삼아, 같은 번호로 다시 보낸 배치가 적재 없이 성공으로 답해질 수 있었기 때문입니다.
72
+ 이제 번호를 다시 매겨 보내므로 그렇게 가려지지 않습니다(엔진 r3525 에서 계약 확인: 이 두 거절은 그 시도가
73
+ 적재되지 않은 것이 확정입니다).
74
+ - FLUSH 와 겹친 구간에서는 **파티션 안 순서가 바뀔 수 있습니다** - 거절된 배치가 이미 나가 있던 뒤 배치보다 늦게
75
+ 적재됩니다(비멱등과 같습니다).
76
+ - 앞선 시도의 결과를 모르는 배치(연결 끊김으로 답이 없었거나 엔진이 "결과 모름" 으로 답한 것)가 이 거절을 받으면
77
+ 번호를 다시 매기지 않고 **같은 번호로** 다시 보냅니다. epoch 는 FLUSH 없이도 바뀌므로(데이터는 그대로) 앞선 시도가
78
+ 적재돼 있을 수 있습니다 - 그러면 같은 번호가 중복 응답(원래 좌표)으로 끝나고, FLUSH 였다면 새 토픽이 그 번호로 적재합니다.
79
+ - 가를 수 없는 경우 하나만 앱에 올립니다: 그 재전송에 엔진이 **좌표 없는 중복**으로 답하면
80
+ `MIMQE_TOPIC_EPOCH_MISMATCH (commit in doubt - ... answered duplicate without coordinates; the batch may not be stored)` 입니다.
81
+ 이미 적재돼 있는 것인지, FLUSH 로 새로 만든 토픽에서 뒤 배치에 가려진 것인지 클라가 알 수 없습니다.
82
+ - 재시도 예산은 그대로입니다(`retries` / `retryBackoffMs` / `deliveryTimeoutMs`).
83
+
63
84
  ## 0.19.3 변경 - 코디네이터 이동을 기다려 넘긴다, 문서 (자바 v2.15.3 과 같음, 짝 엔진 7.0.1.3512 이상)
64
85
 
65
86
  - `addNode` / `removeNode`: 파티션 배치(PLACEMENT)를 켠 정의에서는 `replicas` 가 파티션당 복제본 수라 노드를 더해도
@@ -605,7 +626,7 @@ svc.createCluster(c)
605
626
  멱등이 저절로 꺼집니다. `enableIdempotence(True)` 를 명시하고 그 둘과 함께 쓰면 전처럼 생성 때 `ValueError` 입니다.
606
627
  - 멱등이면 토픽을 비우거나(FLUSH) epoch 가 바뀐 순간 걸려 있던 배치를 다시 보내지 않고 실패로 올립니다
607
628
  (`MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH`). 0.7.0 기본처럼 조용히 다시 보내려면 `enableIdempotence(False)` 를
608
- 명시하십시오.
629
+ 명시하십시오. **(0.19.5 부터 멱등도 조용히 다시 보냅니다 - 0.19.5 절)**
609
630
  - `batchSize` 기본값이 **16384** 입니다(파티션 배치 하나의 상한). 0.7.0 은 262144 였습니다 - 처리량이 모자라면
610
631
  `batchSize(262144)` 처럼 늘리십시오.
611
632
  - producer 수 안내: 한 토픽의 producer(연결)는 1~4개면 충분합니다(엔진 7.0.1.3407 실측).
@@ -10,6 +10,27 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
10
10
  ## 설치 방법
11
11
  pip install jetstream-api
12
12
 
13
+ ## 0.19.5 변경 - 멱등 producer 도 FLUSH TOPIC / epoch 변경을 넘긴다 (자바 v2.15.5 와 같음, 짝 엔진 7.0.1.3512 이상)
14
+
15
+ > 0.19.4 는 시험 저장소(test.pypi)에만 올렸고 배포하지 않았습니다. 0.19.4 는 앞선 시도의 결과를 모르는 배치가 이 거절을
16
+ > 받으면 `commit in doubt` 로 올렸는데, FLUSH 순간 복제 확인을 기다리던 배치가 늘 그 경우라(엔진이 "결과 모름" 으로 답합니다)
17
+ > FLUSH 때마다 앱에 드러났습니다(회귀 FLUSHOLDPROD-1). 아래가 고친 동작입니다.
18
+
19
+ - 멱등 producer(기본)가 `MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH` 로 거절된 배치를 **앱에 올리지 않고**
20
+ 메타데이터를 다시 받아 다시 보냅니다. 0.19.3 까지는 멱등이면 실패로 올렸습니다(0.7.1 절) - FLUSH 로 새로 만든 토픽은
21
+ 처음 받은 번호를 기준선으로 삼아, 같은 번호로 다시 보낸 배치가 적재 없이 성공으로 답해질 수 있었기 때문입니다.
22
+ 이제 번호를 다시 매겨 보내므로 그렇게 가려지지 않습니다(엔진 r3525 에서 계약 확인: 이 두 거절은 그 시도가
23
+ 적재되지 않은 것이 확정입니다).
24
+ - FLUSH 와 겹친 구간에서는 **파티션 안 순서가 바뀔 수 있습니다** - 거절된 배치가 이미 나가 있던 뒤 배치보다 늦게
25
+ 적재됩니다(비멱등과 같습니다).
26
+ - 앞선 시도의 결과를 모르는 배치(연결 끊김으로 답이 없었거나 엔진이 "결과 모름" 으로 답한 것)가 이 거절을 받으면
27
+ 번호를 다시 매기지 않고 **같은 번호로** 다시 보냅니다. epoch 는 FLUSH 없이도 바뀌므로(데이터는 그대로) 앞선 시도가
28
+ 적재돼 있을 수 있습니다 - 그러면 같은 번호가 중복 응답(원래 좌표)으로 끝나고, FLUSH 였다면 새 토픽이 그 번호로 적재합니다.
29
+ - 가를 수 없는 경우 하나만 앱에 올립니다: 그 재전송에 엔진이 **좌표 없는 중복**으로 답하면
30
+ `MIMQE_TOPIC_EPOCH_MISMATCH (commit in doubt - ... answered duplicate without coordinates; the batch may not be stored)` 입니다.
31
+ 이미 적재돼 있는 것인지, FLUSH 로 새로 만든 토픽에서 뒤 배치에 가려진 것인지 클라가 알 수 없습니다.
32
+ - 재시도 예산은 그대로입니다(`retries` / `retryBackoffMs` / `deliveryTimeoutMs`).
33
+
13
34
  ## 0.19.3 변경 - 코디네이터 이동을 기다려 넘긴다, 문서 (자바 v2.15.3 과 같음, 짝 엔진 7.0.1.3512 이상)
14
35
 
15
36
  - `addNode` / `removeNode`: 파티션 배치(PLACEMENT)를 켠 정의에서는 `replicas` 가 파티션당 복제본 수라 노드를 더해도
@@ -555,7 +576,7 @@ svc.createCluster(c)
555
576
  멱등이 저절로 꺼집니다. `enableIdempotence(True)` 를 명시하고 그 둘과 함께 쓰면 전처럼 생성 때 `ValueError` 입니다.
556
577
  - 멱등이면 토픽을 비우거나(FLUSH) epoch 가 바뀐 순간 걸려 있던 배치를 다시 보내지 않고 실패로 올립니다
557
578
  (`MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH`). 0.7.0 기본처럼 조용히 다시 보내려면 `enableIdempotence(False)` 를
558
- 명시하십시오.
579
+ 명시하십시오. **(0.19.5 부터 멱등도 조용히 다시 보냅니다 - 0.19.5 절)**
559
580
  - `batchSize` 기본값이 **16384** 입니다(파티션 배치 하나의 상한). 0.7.0 은 262144 였습니다 - 처리량이 모자라면
560
581
  `batchSize(262144)` 처럼 늘리십시오.
561
582
  - producer 수 안내: 한 토픽의 producer(연결)는 1~4개면 충분합니다(엔진 7.0.1.3407 실측).
@@ -1,6 +1,6 @@
1
1
  """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
2
 
3
- __version__ = "0.19.3"
3
+ __version__ = "0.19.5"
4
4
 
5
5
  # --- Admin / Service ---
6
6
  from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser
@@ -216,12 +216,14 @@ class ILProducerConfig:
216
216
  (``MIMQE_CLUSTER_HALTED``, 엔진 r3484 부터)도 같은 번호로 다시 보낸다 - :class:`ILTopicProducer`
217
217
  의 재시도 설명을 보라.
218
218
  서버가 사유를 실어 거절한 요청은 다시 보내도 같은 답이라 재시도하지 않는다.
219
- 예외는 FLUSHED 하나다 - FLUSH TOPIC 과 겹쳐 적재되지 않은 것이 확정된 건이라
220
- 그 레코드만 호출자에게 알리지 않고 다시 보낸다(중복이 생기지 않는다).
221
- 단 :meth:`enableIdempotence` 를 켜면 FLUSHED 도 재시도하지 않고 실패로 올린다 -
222
- flush 로 새로 만든 토픽은 처음 받은 seq 를 기준선으로 삼으므로, 뒤 레코드가 먼저
223
- 들어간 뒤 되돌린 건을 보내면 적재 없이 성공으로 답할 수 있다(조용한 유실).
224
- 비멱등 재시도는 유실도 중복도 없지만, 되돌린 배치보다 먼저 나가 있던 같은 파티션의
219
+ 예외는 FLUSHED / ``MIMQE_TOPIC_EPOCH_MISMATCH`` 다 - FLUSH TOPIC · 토픽 재생성과 겹쳐
220
+ 적재되지 않은 것이 확정된 건이라, 메타데이터를 다시 받고 그 배치를 호출자에게 알리지 않고
221
+ 다시 보낸다(중복이 생기지 않는다). 0.19.5 부터 :meth:`enableIdempotence` 를 켠 producer 도
222
+ 같다 - 번호를 다시 매겨 보내므로 새로 만든 토픽에서 중복으로 가려지지 않는다(0.19.3 까지는
223
+ 멱등이면 실패로 올렸다). 멱등에서 앞선 시도의 결과를 모르는 배치(답 없음 / 결과 모름 응답)는
224
+ 같은 번호로 다시 보내고, 그 재전송에 좌표 없는 중복 응답이 올 때만 ``commit in doubt``
225
+ 사유로 올린다(0.19.5).
226
+ 이 재시도는 유실도 중복도 없지만, 되돌린 배치보다 먼저 나가 있던 같은 파티션의
225
227
  뒤 배치가 먼저 적재돼 **파티션 안 순서가 바뀔 수 있다**(봉투를 겹쳐 보내므로).
226
228
  멱등이면 앞 배치가 거절돼 순서 오류를 받은 배치를 엔진이 알려 준 번호로 다시 매겨
227
229
  보낸다(순서 유지). 재시도 총 시간은 :meth:`deliveryTimeoutMs` 로도 함께 제한된다.
@@ -393,10 +395,11 @@ class ILProducerConfig:
393
395
  이 메서드로 정하지 않으면 ``acks=0`` 이거나 ``maxInFlight`` 가 5 를 넘을 때 멱등이 저절로 꺼진다.
394
396
  명시적으로 켜고 그 둘과 함께 쓰면 :meth:`validate` 가 거부한다.
395
397
 
396
- 멱등이면 토픽 FLUSH / epoch 변경으로 거절된 배치(``MIMQE_TOPIC_FLUSHED`` /
397
- ``MIMQE_TOPIC_EPOCH_MISMATCH``)를 다시 보내지 않고 실패로 올린다 - 엔진이 그 토픽의 번호 상태를
398
- 새로 잡으므로 다시 보낸 배치가 중복으로 가려져 사라질 수 있기 때문이다. 비멱등(False)은 메타데이터를
399
- 다시 받고 조용히 다시 보낸다.
398
+ 토픽 FLUSH / epoch 변경으로 거절된 배치(``MIMQE_TOPIC_FLUSHED`` / ``MIMQE_TOPIC_EPOCH_MISMATCH``)는
399
+ 멱등이든 아니든 메타데이터를 다시 받고 조용히 다시 보낸다(0.19.5 - 그 전의 멱등은 실패로 올렸다).
400
+ 엔진이 그 토픽의 번호 상태를 새로 잡으므로 멱등은 번호를 다시 매겨 보낸다. FLUSH 와 겹친 구간에서는
401
+ **파티션 안 순서가 바뀔 수 있다**. 앞선 시도의 결과를 모르는 배치는 같은 번호로 다시 보내고, 엔진이 좌표 없는
402
+ 중복으로 답할 때만 ``commit in doubt`` 로 올린다(0.19.5).
400
403
 
401
404
  Args:
402
405
  b (bool): True 면 멱등 발행, False 면 비멱등.
@@ -1311,6 +1314,17 @@ def _halted_error(code: int, reason: str, unknown: bool) -> ILOperationException
1311
1314
  return _batch_error(code, reason)
1312
1315
 
1313
1316
 
1317
+ def _stale_error(code: int, reason: str, unknown: bool) -> ILOperationException:
1318
+ """배치별 거절로 끝난 배치의 오류. 멱등에서 앞선 시도의 결과를 모르는 배치가 FLUSHED / epoch 불일치를 받다가 재시도
1319
+ 예산을 다 쓰면 **in doubt** 다 - 이번 시도는 적재되지 않았지만 앞선 시도는 적재됐을 수 있고, epoch 는 FLUSH 없이도
1320
+ 바뀌므로 클라가 가를 수 없다. 앱은 사유의 ``commit in doubt`` 로 가른다."""
1321
+ if unknown and code in (ILC.MIMQ_TOPIC_PUBLISH_FLUSHED, ILC.MIMQ_TOPIC_PUBLISH_EPOCH_MISMATCH):
1322
+ name = _CODE_NAMES.get(code, str(code))
1323
+ return ILOperationException("%s (commit in doubt - an earlier attempt of this batch got no answer and may have been"
1324
+ " stored; last answer: %s)" % (name, reason or name))
1325
+ return _batch_error(code, reason)
1326
+
1327
+
1314
1328
  def _connection_error(cause: BaseException, rejected: bool) -> ILOperationException:
1315
1329
  """연결 오류로 되돌린 배치가 재시도 예산을 다 썼을 때의 오류(0.18.1). 팔로워가 봉투를 거절했으면 적재되지
1316
1330
  않았고, 답 없이 끊겼으면 **in doubt** 다. 둘 다 서버 쪽 사유로 분류된다 - producer 는 멈추지 않는다."""
@@ -1331,7 +1345,7 @@ class _Batch:
1331
1345
 
1332
1346
  __slots__ = ("partition", "created", "batch_no", "buf", "count", "result", "callbacks",
1333
1347
  "closed", "done", "base_seq", "header_done", "header_pid", "retry_count",
1334
- "retry_at", "unknown", "size_drops", "solo", "body", "attr", "packed")
1348
+ "retry_at", "unknown", "epoch_moved", "size_drops", "solo", "body", "attr", "packed")
1335
1349
 
1336
1350
  def __init__(self, partition: int, created: float, batch_no: int, topic: str = "") -> None:
1337
1351
  self.partition = partition
@@ -1353,8 +1367,11 @@ class _Batch:
1353
1367
  self.header_pid = -2
1354
1368
  self.retry_count = 0
1355
1369
  self.retry_at = 0.0
1356
- #: 답 없이 끝난 시도(연결 끊김)가 있었다 - 그때 적재됐을 수 있다
1370
+ #: 답 없이 끝난 시도(연결 끊김) 또는 결과 모름 응답(4174)이 있었다 - 그때 적재됐을 수 있다
1357
1371
  self.unknown = False
1372
+ #: 결과를 모르는 시도 뒤에 토픽 epoch 가 바뀌었다(FLUSHED / 4166 을 받아 같은 번호로 다시 보낸다, 0.19.5).
1373
+ #: 그 뒤 좌표 없는 중복 응답은 "있다" 인지 "FLUSH 로 새로 만든 토픽의 기준선 아래라 가려졌다" 인지 가를 수 없다
1374
+ self.epoch_moved = False
1358
1375
  #: 재시도 보호: 큰 봉투에 실려 응답 없이 끊긴 횟수. 한 번 끊기면 그 뒤로는 혼자 보낸다
1359
1376
  self.size_drops = 0
1360
1377
  self.solo = False
@@ -1448,12 +1465,13 @@ class ILTopicProducer:
1448
1465
  보장:
1449
1466
 
1450
1467
  - **순서**: 한 파티션 안에서는 ``send`` 순서대로 적재된다. 파티션 사이의 순서는 보장하지 않는다.
1451
- 비멱등에서 FLUSH TOPIC 과 겹친 배치를 다시 보낼 때는 이미 나가 있던 같은 파티션의 뒤 배치가
1452
- 먼저 적재될 수 있다.
1468
+ FLUSH TOPIC 과 겹친 배치를 다시 보낼 때는 이미 나가 있던 같은 파티션의 뒤 배치가
1469
+ 먼저 적재될 수 있다(멱등 · 비멱등 모두).
1453
1470
  - **전달**: ``acks=1`` + 재시도 = at-least-once. 멱등(기본 켜짐)이면 producer 안의 재전송이 엔진에서
1454
1471
  중복 제거되어 중복 적재가 없다(적재하지 못한 배치는 future 가 실패로 알린다). 앱이 ``send`` 를 다시
1455
1472
  부르면 새 레코드라 멱등이어도 걸러지지 않는다. ``acks=0`` = at-most-once.
1456
- - **재시도**: 연결 단절, 비멱등의 FLUSHED / epoch 불일치(적재되지 않은 것이 확정된 거절), 멱등의
1473
+ - **재시도**: 연결 단절, FLUSHED / epoch 불일치(적재되지 않은 것이 확정된 거절 - 0.19.5 부터 멱등도
1474
+ 번호를 다시 매겨 다시 보낸다. 앞선 시도의 결과를 모르는 멱등 배치는 같은 번호로 다시 보낸다), 멱등의
1457
1475
  순서 오류, 클러스터 정지(``MIMQE_CLUSTER_HALTED`` - 엔진 r3484 부터, 같은 번호로 다시 보낸다)다.
1458
1476
  정지 중 사유에 ``commit in doubt`` 가 있으면 적재됐을 수 있다 - 멱등이면 재전송이 중복 응답으로 끝나
1459
1477
  한 번만 남고, 비멱등이면 연결 끊김 때처럼 중복될 수 있다. 예산을 다 쓰면 그 사유로 실패한다. 그 밖의 거절(토픽 없음, 너무 큼 등)은 재시도 없이 그 배치의 future 가 실패한다.
@@ -2823,6 +2841,15 @@ class ILTopicProducer:
2823
2841
  last = b.base_seq + b.count - 1
2824
2842
  if last > self._acked_seq[b.partition]:
2825
2843
  self._acked_seq[b.partition] = last
2844
+ if code == ILC.MIMQ_TOPIC_PUBLISH_DUPLICATE and b.epoch_moved and off < 0:
2845
+ # 0.19.5: 결과를 모르는 시도 뒤에 epoch 가 바뀌었고, 같은 번호의 재전송에 좌표 없는 중복 응답이 왔다.
2846
+ # 데이터가 그대로라면 "이미 있다" 지만, FLUSH 였다면 새 토픽의 기준선 아래라 적재 없이 가려진 것이다 -
2847
+ # 클라가 가를 수 없다(엔진 r3525 확인). 성공으로 올리면 조용한 유실일 수 있다
2848
+ freed += self._complete_batch(b, -1, 0, ILOperationException(
2849
+ "MIMQE_TOPIC_EPOCH_MISMATCH (commit in doubt - the topic epoch changed after an attempt of this batch"
2850
+ " ended without a definite answer, and the resend was answered duplicate without coordinates;"
2851
+ " the batch may not be stored)"), release=False)
2852
+ continue
2826
2853
  freed += self._complete_batch(b, off, ts, None, release=False)
2827
2854
  continue
2828
2855
  # 0.19.0: 이 브로커는 그 파티션의 리더가 아니다 - 적재되지 않았다. 리더 표를 고치고 **같은 번호로** 새 리더에
@@ -2870,7 +2897,15 @@ class ILTopicProducer:
2870
2897
  ILC.MIMQ_TOPIC_PUBLISH_TOO_LARGE):
2871
2898
  link.meta_stale = True
2872
2899
  expected = exp if code == ILC.MIMQ_OUT_OF_ORDER_SEQUENCE else -1
2873
- if self._idem and b.base_seq >= 0:
2900
+ stale = code in (ILC.MIMQ_TOPIC_PUBLISH_EPOCH_MISMATCH, ILC.MIMQ_TOPIC_PUBLISH_FLUSHED)
2901
+ if self._idem and b.base_seq >= 0 and stale and b.unknown:
2902
+ # 0.19.5: 결과를 모르는 시도(답 없음 / 4174) 뒤에 epoch 가 바뀌었다 - **같은 번호를 쥐고** 메타데이터를 다시
2903
+ # 받아 보낸다. epoch 는 FLUSH 없이도 바뀐다(엔진 r3525, 데이터는 그대로): 앞선 시도가 적재돼 있으면 같은
2904
+ # 번호가 중복 응답(원 좌표)으로 끝나고, 새 번호였다면 중복 적재다. FLUSH 였다면 새 토픽이 이 번호를 기준선
2905
+ # 으로 받아 적재한다(FLUSH 순간 복제 확인을 기다리던 배치는 엔진이 4174 로 답한다 - 회귀 FLUSHOLDPROD-1).
2906
+ # 좌표 없는 중복 응답만 가를 수 없어 in doubt 로 올린다(위)
2907
+ b.epoch_moved = True
2908
+ elif self._idem and b.base_seq >= 0:
2874
2909
  # 배치별 거절 = 이번 시도는 적재되지 않았다(엔진 계약). 번호를 돌려받고 재번호를 건다.
2875
2910
  # 단 앞선 시도가 답 없이 끝난 배치(unknown)는 그때 적재됐을 수 있다 - 순서 오류가 아니면 번호를
2876
2911
  # 돌려받지 않는다. 돌려받아 다음 새 배치에 주면 그 배치가 엔진에서 중복 응답으로 사라진다(유실).
@@ -2884,7 +2919,7 @@ class ILTopicProducer:
2884
2919
  b.retry_at = now + self._backoff
2885
2920
  again.append(b)
2886
2921
  else:
2887
- freed += self._complete_batch(b, -1, 0, _batch_error(code, reason), release=False)
2922
+ freed += self._complete_batch(b, -1, 0, _stale_error(code, reason, self._idem and b.unknown), release=False)
2888
2923
  except BaseException:
2889
2924
  # 도중에 끊겨도 이미 완결한 배치의 버퍼는 돌려준다
2890
2925
  if freed:
@@ -2927,16 +2962,20 @@ class ILTopicProducer:
2927
2962
  def _can_retry(self, b: _Batch, code: int, out_of_order: bool, now: float) -> bool:
2928
2963
  """되돌려 다시 보낼 배치인가.
2929
2964
 
2930
- 비멱등: FLUSHED / epoch 불일치 - FLUSH TOPIC 과 겹쳐 적재되지 않은 것이 확정된 거절이라 다시 보내도 중복이 없다.
2931
- 멱등: 순서 오류 - 앞 배치가 거절돼 생긴 구멍이다. 엔진이 기다리는 번호로 다시 매겨 보낸다. 멱등 FLUSHED /
2932
- epoch 불일치는 올린다(flush 로 새로 만든 파티션은 처음 받은 seq 를 기준선으로 삼아 순서가 바뀐다).
2965
+ FLUSHED / epoch 불일치(멱등 · 비멱등 모두, 0.19.4): FLUSH TOPIC / 토픽 재생성과 겹쳐 **이번 시도는 적재되지 않은 것이
2966
+ 확정**된 거절이다(엔진 r3525 확인 - 4166 은 봉투를 인큐하기 전, FLUSHED 는 쓰기 전에만 난다). 메타데이터를 다시 받고
2967
+ 다시 보낸다. 멱등은 번호를 다시 매겨 보낸다 - flush 로 새로 만든 파티션은 처음 받은 seq 를 기준선으로 삼으므로, 뒤
2968
+ 배치가 먼저 기준선이 된 뒤 **같은 번호**로 보내면 적재 없이 중복 응답을 받는다(0.19.3 까지 멱등이 이 거절을 앱에 올린
2969
+ 이유). 받아들여진 가장 큰 번호 위로 다시 매기면 그렇게 가려지지 않는다.
2970
+ 멱등에서 **앞선 시도의 결과를 모르는 배치**(답 없음 / 4174)는 번호를 다시 매기지 않고 같은 번호로 다시 보낸다(0.19.5) -
2971
+ epoch 는 FLUSH 없이도 바뀌므로(데이터는 그대로) 앞선 시도가 적재돼 있을 수 있고, 새 번호로 보내면 중복이 된다.
2972
+ 그 재전송에 좌표 없는 중복 응답이 오면 in doubt 로 올린다(_on_ack).
2973
+ 멱등 순서 오류: 앞 배치가 거절돼 생긴 구멍이다. 엔진이 기다리는 번호로 다시 매겨 보낸다.
2933
2974
  예산은 연결 재시도와 같다(retries / retryBackoffMs / deliveryTimeoutMs).
2934
2975
  """
2935
- if self._idem:
2936
- if not out_of_order:
2976
+ if code not in (ILC.MIMQ_TOPIC_PUBLISH_FLUSHED, ILC.MIMQ_TOPIC_PUBLISH_EPOCH_MISMATCH):
2977
+ if not (self._idem and out_of_order):
2937
2978
  return False
2938
- elif code not in (ILC.MIMQ_TOPIC_PUBLISH_FLUSHED, ILC.MIMQ_TOPIC_PUBLISH_EPOCH_MISMATCH):
2939
- return False
2940
2979
  return self._within_budget(b, now)
2941
2980
 
2942
2981
  def _within_budget(self, b: _Batch, now: float) -> bool:
@@ -3203,6 +3242,7 @@ class ILTopicProducer:
3203
3242
  b.base_seq = -1
3204
3243
  b.header_done = False
3205
3244
  b.unknown = False
3245
+ b.epoch_moved = False
3206
3246
  if self._pid_reset:
3207
3247
  self._pid_reset = False
3208
3248
  self._pid_resets += 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetstream-api
3
- Version: 0.19.3
3
+ Version: 0.19.5
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
@@ -60,6 +60,27 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
60
60
  ## 설치 방법
61
61
  pip install jetstream-api
62
62
 
63
+ ## 0.19.5 변경 - 멱등 producer 도 FLUSH TOPIC / epoch 변경을 넘긴다 (자바 v2.15.5 와 같음, 짝 엔진 7.0.1.3512 이상)
64
+
65
+ > 0.19.4 는 시험 저장소(test.pypi)에만 올렸고 배포하지 않았습니다. 0.19.4 는 앞선 시도의 결과를 모르는 배치가 이 거절을
66
+ > 받으면 `commit in doubt` 로 올렸는데, FLUSH 순간 복제 확인을 기다리던 배치가 늘 그 경우라(엔진이 "결과 모름" 으로 답합니다)
67
+ > FLUSH 때마다 앱에 드러났습니다(회귀 FLUSHOLDPROD-1). 아래가 고친 동작입니다.
68
+
69
+ - 멱등 producer(기본)가 `MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH` 로 거절된 배치를 **앱에 올리지 않고**
70
+ 메타데이터를 다시 받아 다시 보냅니다. 0.19.3 까지는 멱등이면 실패로 올렸습니다(0.7.1 절) - FLUSH 로 새로 만든 토픽은
71
+ 처음 받은 번호를 기준선으로 삼아, 같은 번호로 다시 보낸 배치가 적재 없이 성공으로 답해질 수 있었기 때문입니다.
72
+ 이제 번호를 다시 매겨 보내므로 그렇게 가려지지 않습니다(엔진 r3525 에서 계약 확인: 이 두 거절은 그 시도가
73
+ 적재되지 않은 것이 확정입니다).
74
+ - FLUSH 와 겹친 구간에서는 **파티션 안 순서가 바뀔 수 있습니다** - 거절된 배치가 이미 나가 있던 뒤 배치보다 늦게
75
+ 적재됩니다(비멱등과 같습니다).
76
+ - 앞선 시도의 결과를 모르는 배치(연결 끊김으로 답이 없었거나 엔진이 "결과 모름" 으로 답한 것)가 이 거절을 받으면
77
+ 번호를 다시 매기지 않고 **같은 번호로** 다시 보냅니다. epoch 는 FLUSH 없이도 바뀌므로(데이터는 그대로) 앞선 시도가
78
+ 적재돼 있을 수 있습니다 - 그러면 같은 번호가 중복 응답(원래 좌표)으로 끝나고, FLUSH 였다면 새 토픽이 그 번호로 적재합니다.
79
+ - 가를 수 없는 경우 하나만 앱에 올립니다: 그 재전송에 엔진이 **좌표 없는 중복**으로 답하면
80
+ `MIMQE_TOPIC_EPOCH_MISMATCH (commit in doubt - ... answered duplicate without coordinates; the batch may not be stored)` 입니다.
81
+ 이미 적재돼 있는 것인지, FLUSH 로 새로 만든 토픽에서 뒤 배치에 가려진 것인지 클라가 알 수 없습니다.
82
+ - 재시도 예산은 그대로입니다(`retries` / `retryBackoffMs` / `deliveryTimeoutMs`).
83
+
63
84
  ## 0.19.3 변경 - 코디네이터 이동을 기다려 넘긴다, 문서 (자바 v2.15.3 과 같음, 짝 엔진 7.0.1.3512 이상)
64
85
 
65
86
  - `addNode` / `removeNode`: 파티션 배치(PLACEMENT)를 켠 정의에서는 `replicas` 가 파티션당 복제본 수라 노드를 더해도
@@ -605,7 +626,7 @@ svc.createCluster(c)
605
626
  멱등이 저절로 꺼집니다. `enableIdempotence(True)` 를 명시하고 그 둘과 함께 쓰면 전처럼 생성 때 `ValueError` 입니다.
606
627
  - 멱등이면 토픽을 비우거나(FLUSH) epoch 가 바뀐 순간 걸려 있던 배치를 다시 보내지 않고 실패로 올립니다
607
628
  (`MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH`). 0.7.0 기본처럼 조용히 다시 보내려면 `enableIdempotence(False)` 를
608
- 명시하십시오.
629
+ 명시하십시오. **(0.19.5 부터 멱등도 조용히 다시 보냅니다 - 0.19.5 절)**
609
630
  - `batchSize` 기본값이 **16384** 입니다(파티션 배치 하나의 상한). 0.7.0 은 262144 였습니다 - 처리량이 모자라면
610
631
  `batchSize(262144)` 처럼 늘리십시오.
611
632
  - producer 수 안내: 한 토픽의 producer(연결)는 1~4개면 충분합니다(엔진 7.0.1.3407 실측).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jetstream-api"
7
- version = "0.19.3"
7
+ version = "0.19.5"
8
8
  description = "Python client API for iLink M.O.M. (Message Oriented Middleware)"
9
9
  authors = [{name = "snowjeans"}]
10
10
  readme = "README.md"
File without changes