jetstream-api 0.6.10__tar.gz → 0.7.0__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 (27) hide show
  1. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/PKG-INFO +55 -7
  2. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/README.md +54 -6
  3. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/__init__.py +1 -1
  4. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/admin.py +37 -0
  5. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/admintopic.py +3 -3
  6. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/common.py +40 -14
  7. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/connectivity.py +61 -1
  8. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/exception.py +1 -1
  9. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/msg.py +140 -302
  10. jetstream_api-0.7.0/ilink/producer.py +2277 -0
  11. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/properties.py +38 -15
  12. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/qmgr.py +58 -13
  13. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/topic.py +33 -17
  14. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/jetstream_api.egg-info/PKG-INFO +55 -7
  15. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/pyproject.toml +1 -1
  16. jetstream_api-0.6.10/ilink/producer.py +0 -1427
  17. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/LICENSE.txt +0 -0
  18. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/partitioner.py +0 -0
  19. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/pattern.py +0 -0
  20. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/py.typed +0 -0
  21. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/ilink/util.py +0 -0
  22. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/jetstream_api.egg-info/SOURCES.txt +0 -0
  23. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/jetstream_api.egg-info/dependency_links.txt +0 -0
  24. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/jetstream_api.egg-info/requires.txt +0 -0
  25. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/jetstream_api.egg-info/top_level.txt +0 -0
  26. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/setup.cfg +0 -0
  27. {jetstream_api-0.6.10 → jetstream_api-0.7.0}/tests/test_example.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetstream-api
3
- Version: 0.6.10
3
+ Version: 0.7.0
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,54 @@ Python으로 작성된 ILink 클라이언트 API입니다.
54
54
  ## 설치 방법
55
55
  pip install jetstream-api
56
56
 
57
+ ## 0.7.0 변경 - 발행 v2: producer 하나 = 토픽 하나, 파티션별 배치, 봉투 겹쳐 보내기 (엔진 v2 필요)
58
+
59
+ 발행 경로를 엔진의 새 발행 형식(v2)으로 바꿨습니다. **엔진도 v2 판이어야 합니다** - 옛 엔진에는 발행이 되지 않고
60
+ (`MIMQE_NOT_SUPPORTED`), 옛 클라이언트(0.6.x)는 v2 엔진에 발행할 수 없습니다. 둘을 함께 올리십시오.
61
+
62
+ - **producer 하나 = 토픽 하나 = 연결 하나.** `qmgr.create_producer("ORDER.EVENT", config)` 로 만듭니다. 생성할 때 토픽의
63
+ 파티션 수를 받아 두므로 없는 토픽이면 생성 단계에서 `MIMQE_OBJECT_NOT_FOUND` 로 실패합니다. 토픽이 여러 개면 토픽마다
64
+ producer 를 만들고, 연결이 더 필요하면 producer 를 더 만듭니다(예: 발행 스레드마다 하나).
65
+ - 레코드 토픽이 producer 토픽과 다르면 `send()` 가 `MIMQE_INVALID_ARGUMENT` 로 거부합니다.
66
+ - 옛 `create_producer(config)` / `ILTopicProducer(host, port, name, config)` 모양으로 부르면 `TypeError` 가 납니다.
67
+ - 배치는 **파티션마다** 모입니다. `batchSize`(기본 **262144**, 자바와 같음)는 파티션 배치 하나의 상한입니다. 보낼 때는
68
+ 준비된 파티션 배치들을 봉투 하나에 `maxRequestSize` 까지 싣고, 봉투를 `maxInFlight`(기본 5) 개까지 겹쳐 보냅니다.
69
+ 완결은 I/O 스레드가 하므로 `lingerMs=0` 이어도 `send()` 가 돌아온 시점에 끝나 있다는 보장은 없습니다 - 동기 발행은
70
+ `send(rec).get()` 입니다. `send(rec, callback)` 으로 콜백(`callback(metadata, exc)`)을 걸 수 있습니다.
71
+ - 멱등 발행은 파티션 배치 단위로 번호를 매깁니다. 멱등이면 `maxInFlight` 는 5 이하여야 합니다.
72
+ - 버퍼(`bufferMemory`)가 차면 `send()` 가 `maxBlockMs` 만큼 기다렸다가 `MIMQE_QUEUE_FULL (buffer memory full ...)` 로
73
+ 거부합니다(전에는 그 자리에서 비웠습니다). 부하 문제이므로 다시 시도할 수 있습니다.
74
+ - 크기 상한: 레코드 하나가 혼자 실린 봉투가 `maxRequestSize` 이하, key 와 속성은 각각 99,999바이트까지입니다. 넘으면
75
+ `send()` 가 `MIMQE_MSG_SIZE_OVER (...)` 로 거부합니다. 배치는 토픽 세그먼트 크기에도 맞춰 끊습니다. 봉투 건수 상한(10만 건)은
76
+ 없어졌습니다.
77
+ - 멱등 발행에서 리더가 바뀌는 순간, 팔로워가 거절한 배치 뒤의 배치가 먼저 적재되면 앞 배치는 번호가 틈으로 남습니다. 이 배치는
78
+ `MIMQE_OUT_OF_ORDER_SEQUENCE (sequence gap ...)` 로 **실패**합니다(적재되지 않았습니다). 전에는 이 경우 성공으로 보고되고
79
+ 레코드가 사라질 수 있었습니다.
80
+ - 소비자 배치 읽기가 엔진 저장 v4 응답(2042)을 받습니다. 엔진은 배치를 쪼개지 않아 요청한 개수보다 많이 줄 수 있습니다.
81
+ `read_batch(max, t)` 는 그래도 **max 이하**를 돌려주고, 넘친 레코드는 구독이 들고 있다가 다음 `read` / `read_batch` 가
82
+ 먼저 줍니다. `COMMIT_AUTO` / `COMMIT_MANUAL` 은 앱에 건넨 레코드까지만 커밋하므로 들고 있던 레코드는 끊기면 다시 옵니다.
83
+ `COMMIT_IMMEDIATE` 는 엔진이 읽는 순간 배치 끝까지 커밋하므로 들고 있던 레코드도 이미 커밋된 상태라, 앱에 건네기 전에
84
+ 끊기면 다시 오지 않습니다(at-most-once). `seek` 는 들고 있던 레코드를 버립니다. 옛 배치 응답(2024)은 받지 않습니다 -
85
+ 배치 읽기에는 저장 v4 엔진이 필요합니다.
86
+ - **없어진 API**: `ILTopicFrameMsg` 의 발행 조립(`createPublish`, `buildBatchPublishEnvelope`, `patchPidSeq`,
87
+ `setAckMode`, `setPartition`, `setPidSeq`, `PUBLISH_*_OFFSET`)과 옛 응답 파싱(`getCorrId`, `getResultCode`, `getReason`,
88
+ `getResultPartition` / `Offset` / `Timestamp` / `Reason`), 상수 `ILC.MIMQ_PUBLISH_MESSAGE` / `MIMQ_PUBLISH_ACK_MESSAGE` /
89
+ `MIMQ_BATCH_PUBLISH_MESSAGE` / `MIMQ_BATCH_PUBLISH_ACK_MESSAGE`. `ILTopicFrameMsg` 는 구독 전달 수신 전용이 됐습니다.
90
+ - 추가: `ILTopicProducer.get_topic()` / `get_partition_count()`, `ILC.MIMQ_TOPIC_PUBLISH_V2_MESSAGE`(2040) /
91
+ `MIMQ_TOPIC_PUBLISH_V2_ACK_MESSAGE`(2041), 결과 코드 `ILC.MIMQ_TOPIC_PUBLISH_DUPLICATE`(4164) ~ `FOLLOWER_NODE`(4172),
92
+ `ILC.MIMQ_TOPIC_BATCH_V4_MESSAGE`(2042)와 수신 전용 `ILTopicBatchV4Msg`.
93
+
94
+ ## 0.6.11 변경 - 메시지 크기 상한(기본 128MiB), 재시도 보호, 클러스터 편의 개선
95
+
96
+ - **메시지(프레임) 크기 상한의 기본값이 128MiB 입니다**(엔진 `Runtime@MaxFrameLength` 기본과 같습니다). 넘는 메시지는 **보내기 전에** `MIMQE_MSG_SIZE_OVER` 로 막고 연결은 그대로 둡니다.
97
+ - 엔진은 서버의 상한을 알려 주지 않습니다. 서버에서 상한을 올렸다면 클라이언트도 같이 올리십시오 - 연결은 `setMaxFrameLength(n)`(`ILQmgr` / `ILAdminService`), producer 는 `ILProducerConfig.maxFrameLength(n)` 입니다. 범위는 1,048,576 ~ 999,999,999 입니다.
98
+ - producer 레코드 한 건의 실제 상한은 `maxRequestSize` 와 `maxFrameLength - 25`(헤더) 중 작은 쪽입니다.
99
+ - **재시도 보호**: 1MiB 를 넘는 레코드를 보낸 직후 응답 없이 연결이 **연속 2번** 끊기면, producer 는 재시도를 멈추고 그 레코드를 `MIMQE_MSG_SIZE_OVER` 로 실패시킵니다. 서버 상한이 클라이언트 설정보다 작을 때 같은 큰 레코드를 시한이 다할 때까지 되풀이해 보내지 않게 하려는 것입니다. 큰 레코드는 따로 보내므로 옆 레코드는 영향을 받지 않습니다.
100
+ - `ILClusterNode("QM1", "10.0.0.12", 21001, 27091)` 처럼 자바와 같은 순서로 만들 수 있습니다(`ILClusterProperty("C1", 27091)` 도 같습니다).
101
+ - `getAuthorityHolderType()` 은 권한 보유자를 `"MAIN"` / `"SUB"` / `""` 로 줍니다. `getAuthorityHolder()` 는 선 코드(`"M"` / `"S"`)라 `clusterType` 과 바로 비교하면 늘 거짓입니다.
102
+
103
+ ★ 엔진 7.0.1.3404 전에는 엔진 쪽 상한이 999,999,999 였습니다. 그런 엔진에 128MiB 를 넘는 메시지를 보내려면 위 설정을 올리십시오.
104
+
57
105
  ## 0.6.10 변경 - 클러스터 관리 API 를 판사/조정자 모델로 (클러스터는 엔진 7.0.1.3391 이상)
58
106
 
59
107
  클러스터 하나는 관리 서비스 셋(판사 · MAIN · SUB)에 **각자 정의를 만듭니다.** 한 서비스에 만들면 나머지에 퍼지던 옛 방식은 엔진에서 없어졌습니다.
@@ -119,12 +167,12 @@ sub.stop_listening()
119
167
 
120
168
  ### 발행
121
169
 
122
- 발행은 producer 단일 경로입니다. 동기 발행은 `send().get()`을 씁니다.
170
+ 발행은 producer 단일 경로입니다. producer 하나는 토픽 하나에 묶입니다(0.7.0). 동기 발행은 `send().get()`을 씁니다.
123
171
 
124
172
  ```python
125
173
  from ilink.producer import ILProducerConfig, ILProducerRecord
126
174
 
127
- prod = qmgr.create_producer(ILProducerConfig())
175
+ prod = qmgr.create_producer("ORDER.EVENT", ILProducerConfig())
128
176
  meta = prod.send(ILProducerRecord("ORDER.EVENT", "k1", b"payload",
129
177
  properties={"trace-id": "abc"})).get()
130
178
  print(meta.get_partition(), meta.get_offset())
@@ -393,13 +441,13 @@ from ilink.producer import ILProducerConfig
393
441
 
394
442
  cfg = (ILProducerConfig()
395
443
  .acks(1).lingerMs(5).batchSize(32768)
396
- .maxRequestSize(1048576) # 레코드 1건 상한 - 넘으면 send()가 거부
397
- .bufferMemory(33554432) # 미전송 누적 상한
444
+ .maxRequestSize(1048576) # 봉투 하나의 상한(레코드 1건도) - 넘는 레코드는 send()가 거부
445
+ .bufferMemory(33554432) # 미전송 누적 상한 - 차면 maxBlockMs 까지 기다린다
398
446
  .retries(3).retryBackoffMs(100)
399
447
  .deliveryTimeoutMs(120000) # 재시도를 포함한 완결 시한
400
- .enableIdempotence(True)) # PID/시퀀스 중복 제거 (acks=1 필요)
448
+ .enableIdempotence(True)) # PID/시퀀스 중복 제거 (acks=1, maxInFlight 5 이하 필요)
401
449
 
402
- prod = qmgr.create_producer(cfg)
450
+ prod = qmgr.create_producer("ORDER.EVENT", cfg) # 0.7.0: producer 하나 = 토픽 하나
403
451
  print(prod.get_producer_id(), prod.is_connected())
404
452
  ```
405
453
 
@@ -8,6 +8,54 @@ Python으로 작성된 ILink 클라이언트 API입니다.
8
8
  ## 설치 방법
9
9
  pip install jetstream-api
10
10
 
11
+ ## 0.7.0 변경 - 발행 v2: producer 하나 = 토픽 하나, 파티션별 배치, 봉투 겹쳐 보내기 (엔진 v2 필요)
12
+
13
+ 발행 경로를 엔진의 새 발행 형식(v2)으로 바꿨습니다. **엔진도 v2 판이어야 합니다** - 옛 엔진에는 발행이 되지 않고
14
+ (`MIMQE_NOT_SUPPORTED`), 옛 클라이언트(0.6.x)는 v2 엔진에 발행할 수 없습니다. 둘을 함께 올리십시오.
15
+
16
+ - **producer 하나 = 토픽 하나 = 연결 하나.** `qmgr.create_producer("ORDER.EVENT", config)` 로 만듭니다. 생성할 때 토픽의
17
+ 파티션 수를 받아 두므로 없는 토픽이면 생성 단계에서 `MIMQE_OBJECT_NOT_FOUND` 로 실패합니다. 토픽이 여러 개면 토픽마다
18
+ producer 를 만들고, 연결이 더 필요하면 producer 를 더 만듭니다(예: 발행 스레드마다 하나).
19
+ - 레코드 토픽이 producer 토픽과 다르면 `send()` 가 `MIMQE_INVALID_ARGUMENT` 로 거부합니다.
20
+ - 옛 `create_producer(config)` / `ILTopicProducer(host, port, name, config)` 모양으로 부르면 `TypeError` 가 납니다.
21
+ - 배치는 **파티션마다** 모입니다. `batchSize`(기본 **262144**, 자바와 같음)는 파티션 배치 하나의 상한입니다. 보낼 때는
22
+ 준비된 파티션 배치들을 봉투 하나에 `maxRequestSize` 까지 싣고, 봉투를 `maxInFlight`(기본 5) 개까지 겹쳐 보냅니다.
23
+ 완결은 I/O 스레드가 하므로 `lingerMs=0` 이어도 `send()` 가 돌아온 시점에 끝나 있다는 보장은 없습니다 - 동기 발행은
24
+ `send(rec).get()` 입니다. `send(rec, callback)` 으로 콜백(`callback(metadata, exc)`)을 걸 수 있습니다.
25
+ - 멱등 발행은 파티션 배치 단위로 번호를 매깁니다. 멱등이면 `maxInFlight` 는 5 이하여야 합니다.
26
+ - 버퍼(`bufferMemory`)가 차면 `send()` 가 `maxBlockMs` 만큼 기다렸다가 `MIMQE_QUEUE_FULL (buffer memory full ...)` 로
27
+ 거부합니다(전에는 그 자리에서 비웠습니다). 부하 문제이므로 다시 시도할 수 있습니다.
28
+ - 크기 상한: 레코드 하나가 혼자 실린 봉투가 `maxRequestSize` 이하, key 와 속성은 각각 99,999바이트까지입니다. 넘으면
29
+ `send()` 가 `MIMQE_MSG_SIZE_OVER (...)` 로 거부합니다. 배치는 토픽 세그먼트 크기에도 맞춰 끊습니다. 봉투 건수 상한(10만 건)은
30
+ 없어졌습니다.
31
+ - 멱등 발행에서 리더가 바뀌는 순간, 팔로워가 거절한 배치 뒤의 배치가 먼저 적재되면 앞 배치는 번호가 틈으로 남습니다. 이 배치는
32
+ `MIMQE_OUT_OF_ORDER_SEQUENCE (sequence gap ...)` 로 **실패**합니다(적재되지 않았습니다). 전에는 이 경우 성공으로 보고되고
33
+ 레코드가 사라질 수 있었습니다.
34
+ - 소비자 배치 읽기가 엔진 저장 v4 응답(2042)을 받습니다. 엔진은 배치를 쪼개지 않아 요청한 개수보다 많이 줄 수 있습니다.
35
+ `read_batch(max, t)` 는 그래도 **max 이하**를 돌려주고, 넘친 레코드는 구독이 들고 있다가 다음 `read` / `read_batch` 가
36
+ 먼저 줍니다. `COMMIT_AUTO` / `COMMIT_MANUAL` 은 앱에 건넨 레코드까지만 커밋하므로 들고 있던 레코드는 끊기면 다시 옵니다.
37
+ `COMMIT_IMMEDIATE` 는 엔진이 읽는 순간 배치 끝까지 커밋하므로 들고 있던 레코드도 이미 커밋된 상태라, 앱에 건네기 전에
38
+ 끊기면 다시 오지 않습니다(at-most-once). `seek` 는 들고 있던 레코드를 버립니다. 옛 배치 응답(2024)은 받지 않습니다 -
39
+ 배치 읽기에는 저장 v4 엔진이 필요합니다.
40
+ - **없어진 API**: `ILTopicFrameMsg` 의 발행 조립(`createPublish`, `buildBatchPublishEnvelope`, `patchPidSeq`,
41
+ `setAckMode`, `setPartition`, `setPidSeq`, `PUBLISH_*_OFFSET`)과 옛 응답 파싱(`getCorrId`, `getResultCode`, `getReason`,
42
+ `getResultPartition` / `Offset` / `Timestamp` / `Reason`), 상수 `ILC.MIMQ_PUBLISH_MESSAGE` / `MIMQ_PUBLISH_ACK_MESSAGE` /
43
+ `MIMQ_BATCH_PUBLISH_MESSAGE` / `MIMQ_BATCH_PUBLISH_ACK_MESSAGE`. `ILTopicFrameMsg` 는 구독 전달 수신 전용이 됐습니다.
44
+ - 추가: `ILTopicProducer.get_topic()` / `get_partition_count()`, `ILC.MIMQ_TOPIC_PUBLISH_V2_MESSAGE`(2040) /
45
+ `MIMQ_TOPIC_PUBLISH_V2_ACK_MESSAGE`(2041), 결과 코드 `ILC.MIMQ_TOPIC_PUBLISH_DUPLICATE`(4164) ~ `FOLLOWER_NODE`(4172),
46
+ `ILC.MIMQ_TOPIC_BATCH_V4_MESSAGE`(2042)와 수신 전용 `ILTopicBatchV4Msg`.
47
+
48
+ ## 0.6.11 변경 - 메시지 크기 상한(기본 128MiB), 재시도 보호, 클러스터 편의 개선
49
+
50
+ - **메시지(프레임) 크기 상한의 기본값이 128MiB 입니다**(엔진 `Runtime@MaxFrameLength` 기본과 같습니다). 넘는 메시지는 **보내기 전에** `MIMQE_MSG_SIZE_OVER` 로 막고 연결은 그대로 둡니다.
51
+ - 엔진은 서버의 상한을 알려 주지 않습니다. 서버에서 상한을 올렸다면 클라이언트도 같이 올리십시오 - 연결은 `setMaxFrameLength(n)`(`ILQmgr` / `ILAdminService`), producer 는 `ILProducerConfig.maxFrameLength(n)` 입니다. 범위는 1,048,576 ~ 999,999,999 입니다.
52
+ - producer 레코드 한 건의 실제 상한은 `maxRequestSize` 와 `maxFrameLength - 25`(헤더) 중 작은 쪽입니다.
53
+ - **재시도 보호**: 1MiB 를 넘는 레코드를 보낸 직후 응답 없이 연결이 **연속 2번** 끊기면, producer 는 재시도를 멈추고 그 레코드를 `MIMQE_MSG_SIZE_OVER` 로 실패시킵니다. 서버 상한이 클라이언트 설정보다 작을 때 같은 큰 레코드를 시한이 다할 때까지 되풀이해 보내지 않게 하려는 것입니다. 큰 레코드는 따로 보내므로 옆 레코드는 영향을 받지 않습니다.
54
+ - `ILClusterNode("QM1", "10.0.0.12", 21001, 27091)` 처럼 자바와 같은 순서로 만들 수 있습니다(`ILClusterProperty("C1", 27091)` 도 같습니다).
55
+ - `getAuthorityHolderType()` 은 권한 보유자를 `"MAIN"` / `"SUB"` / `""` 로 줍니다. `getAuthorityHolder()` 는 선 코드(`"M"` / `"S"`)라 `clusterType` 과 바로 비교하면 늘 거짓입니다.
56
+
57
+ ★ 엔진 7.0.1.3404 전에는 엔진 쪽 상한이 999,999,999 였습니다. 그런 엔진에 128MiB 를 넘는 메시지를 보내려면 위 설정을 올리십시오.
58
+
11
59
  ## 0.6.10 변경 - 클러스터 관리 API 를 판사/조정자 모델로 (클러스터는 엔진 7.0.1.3391 이상)
12
60
 
13
61
  클러스터 하나는 관리 서비스 셋(판사 · MAIN · SUB)에 **각자 정의를 만듭니다.** 한 서비스에 만들면 나머지에 퍼지던 옛 방식은 엔진에서 없어졌습니다.
@@ -73,12 +121,12 @@ sub.stop_listening()
73
121
 
74
122
  ### 발행
75
123
 
76
- 발행은 producer 단일 경로입니다. 동기 발행은 `send().get()`을 씁니다.
124
+ 발행은 producer 단일 경로입니다. producer 하나는 토픽 하나에 묶입니다(0.7.0). 동기 발행은 `send().get()`을 씁니다.
77
125
 
78
126
  ```python
79
127
  from ilink.producer import ILProducerConfig, ILProducerRecord
80
128
 
81
- prod = qmgr.create_producer(ILProducerConfig())
129
+ prod = qmgr.create_producer("ORDER.EVENT", ILProducerConfig())
82
130
  meta = prod.send(ILProducerRecord("ORDER.EVENT", "k1", b"payload",
83
131
  properties={"trace-id": "abc"})).get()
84
132
  print(meta.get_partition(), meta.get_offset())
@@ -347,13 +395,13 @@ from ilink.producer import ILProducerConfig
347
395
 
348
396
  cfg = (ILProducerConfig()
349
397
  .acks(1).lingerMs(5).batchSize(32768)
350
- .maxRequestSize(1048576) # 레코드 1건 상한 - 넘으면 send()가 거부
351
- .bufferMemory(33554432) # 미전송 누적 상한
398
+ .maxRequestSize(1048576) # 봉투 하나의 상한(레코드 1건도) - 넘는 레코드는 send()가 거부
399
+ .bufferMemory(33554432) # 미전송 누적 상한 - 차면 maxBlockMs 까지 기다린다
352
400
  .retries(3).retryBackoffMs(100)
353
401
  .deliveryTimeoutMs(120000) # 재시도를 포함한 완결 시한
354
- .enableIdempotence(True)) # PID/시퀀스 중복 제거 (acks=1 필요)
402
+ .enableIdempotence(True)) # PID/시퀀스 중복 제거 (acks=1, maxInFlight 5 이하 필요)
355
403
 
356
- prod = qmgr.create_producer(cfg)
404
+ prod = qmgr.create_producer("ORDER.EVENT", cfg) # 0.7.0: producer 하나 = 토픽 하나
357
405
  print(prod.get_producer_id(), prod.is_connected())
358
406
  ```
359
407
 
@@ -1,6 +1,6 @@
1
1
  """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
2
 
3
- __version__ = "0.6.10"
3
+ __version__ = "0.7.0"
4
4
 
5
5
  # --- Admin / Service ---
6
6
  from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser
@@ -275,6 +275,43 @@ class ILAdminService:
275
275
  """
276
276
  return self.service.getSessionTimeout()
277
277
 
278
+ def set_max_frame_length(self, max_bytes: int) -> None:
279
+ """이 연결로 보낼 **프레임 길이 상한**(바이트)을 정한다. 기본 134217728(128MiB).
280
+
281
+ 엔진은 받는 프레임이 큐관리자의 ``Runtime@MaxFrameLength``(기본 128MiB)를 넘으면 **사유 없이
282
+ 연결을 끊는다.** 엔진이 그 값을 알려 주지 않으므로 라이브러리는 이 값으로 **보내기 전에**
283
+ ``MIMQE_MSG_SIZE_OVER`` 를 낸다(연결은 그대로 둔다). 서버에서 상한을 올렸다면 여기서도 같이 올린다.
284
+
285
+ Args:
286
+ max_bytes (int): 상한(바이트). 1048576 ~ 999999999 로 보정된다(엔진 설정 범위와 같다).
287
+ 999,999,999 는 길이 칸(10자리)의 절대 상한이다.
288
+
289
+ Note:
290
+ 엔진 7.0.1.3404 전에는 엔진 쪽 상한이 999,999,999 였다. 그런 엔진에 128MiB 를 넘는 메시지를
291
+ 보내려면 이 값을 올린다.
292
+
293
+ Example::
294
+
295
+ svc.set_max_frame_length(512 * 1024 * 1024) # 서버 Runtime@MaxFrameLength 도 512MiB 일 때
296
+ """
297
+ self.service.set_max_frame_length(max_bytes)
298
+
299
+ def setMaxFrameLength(self, maxBytes: int) -> None:
300
+ """:meth:`set_max_frame_length` 의 camelCase 별칭."""
301
+ self.set_max_frame_length(maxBytes)
302
+
303
+ def get_max_frame_length(self) -> int:
304
+ """이 연결의 프레임 길이 상한(바이트). 기본 134217728(128MiB).
305
+
306
+ Returns:
307
+ int: 상한(바이트).
308
+ """
309
+ return self.service.get_max_frame_length()
310
+
311
+ def getMaxFrameLength(self) -> int:
312
+ """:meth:`get_max_frame_length` 의 camelCase 별칭."""
313
+ return self.get_max_frame_length()
314
+
278
315
  def getSessionId(self) -> str:
279
316
  """접속 시 서버가 발급한 세션 식별자.
280
317
 
@@ -9,9 +9,9 @@
9
9
 
10
10
  .. warning:: 관리 연결에서는 배치가 없다
11
11
 
12
- 레코드 **한 건에 한 번 왕복**한다. 릴레이가 1요청-1응답 고정이라 배치 read
13
- (``TOPIC_BATCH_MESSAGE`` 봉투 + 뒤따르는 프레임 N개)와 배치 발행
14
- (``BATCH_PUBLISH`` 봉투)은 엔진이 ``MIMQE_UNSUPPORTED_ON_RELAY`` 로 거부한다.
12
+ 레코드 **한 건에 한 번 왕복**한다. 이 모듈은 배치 read 요청을 보내지 않고, 발행은
13
+ 단건(``TOPIC_PUBLISH_ONE``)뿐이다. 데이터 연결의 발행 봉투(v2, 2040)는 ilsvc 가
14
+ ``MIMQE_INVALID_MSG_TYPE`` 으로 거절해 큐 관리자에 닿지 않는다.
15
15
 
16
16
  :meth:`ILAdminTopicSubscription.read_batch` 는 제공하지만 **클라이언트가 단건
17
17
  read 를 반복**하는 것이라 왕복 수가 줄지 않는다. 대량 소비가 필요하면 리스너
@@ -51,13 +51,6 @@ class ILC:
51
51
  **파이썬 Enum** ``option`` :class:`MatchOption`
52
52
  채널 속성 :class:`~ilink.properties.ILChannelType` 등
53
53
  ======================= =================== ===================================
54
-
55
- .. warning::
56
-
57
- ``ack_mode`` 는 **같은 이름인데 값 집합이 두 가지**다.
58
- :meth:`~ilink.msg.ILTopicFrameMsg.setAckMode` 는 ``"1"``/``"0"``,
59
- :meth:`~ilink.msg.ILTopicFrameMsg.buildBatchPublishEnvelope` 는
60
- ``"A"``/``"I"``/``"M"`` 를 받는다.
61
54
  """
62
55
 
63
56
  def __init__(self):
@@ -76,6 +69,13 @@ class ILC:
76
69
  #: 엔진은 ``MIMQE_DATA_TOO_BIG`` 으로 **사유 응답 없이 연결을 끊는다** - 그래서 라이브러리가 보내기
77
70
  #: 전에 ``MIMQE_MSG_SIZE_OVER`` 로 막는다(연결은 그대로 둔다).
78
71
  MIMQ_MAX_FRAME_LENGTH = 999_999_999
72
+ #: 엔진 수신 프레임 길이 상한의 **기본값**(128MiB, 엔진 SOCKGUARD). 큐관리자마다
73
+ #: ``Runtime@MaxFrameLength`` 로 1,048,576 ~ 999,999,999 까지 올릴 수 있고(재기동 필요), 엔진은 그 값을
74
+ #: 클라에 알리지 않는다. 연결과 producer 는 이 값을 기본 상한으로 쓰고, 서버에서 올렸으면
75
+ #: ``setMaxFrameLength`` / ``ILProducerConfig.maxFrameLength`` 로 같이 올린다.
76
+ MIMQ_DEFAULT_MAX_FRAME_LENGTH = 134_217_728
77
+ #: 프레임 길이 상한 설정의 하한(엔진 ``Runtime@MaxFrameLength`` 하한과 같다).
78
+ MIMQ_MIN_MAX_FRAME_LENGTH = 1_048_576
79
79
  SZ_CODE = 5
80
80
  SZ_CODE_LONG = 6
81
81
  SZ_JMS_HEADER = 394
@@ -245,15 +245,21 @@ class ILC:
245
245
  MIMQ_REPORT_MESSAGE = 2002
246
246
  MIMQ_LONG_REPORT_MESSAGE = 2003
247
247
  MIMQ_LONG_REQUEST_MESSAGE = 2004
248
- # pub/sub 데이터 평면 전용 타입 (Phase 3~7).
249
- # 단독 PUBLISH(2020)는 엔진이 더 이상 받지 않는다 - 발행은 BATCH_PUBLISH(2025) 봉투로만 간다
250
- # (bare 2020 = 프로토콜 위반으로 연결 종료).
251
- MIMQ_PUBLISH_MESSAGE = 2020
252
- MIMQ_PUBLISH_ACK_MESSAGE = 2021
248
+ # pub/sub 데이터 평면 전용 타입.
249
+ # 0.7.0: 옛 발행 프레임 2020(PUBLISH) / 2021(PUBLISH_ACK) / 2025(BATCH_PUBLISH) / 2026(BATCH_PUBLISH_ACK)은
250
+ # 엔진과 같은 판에서 걷어냈다 - 발행은 아래 2040 / 2041 뿐이다.
253
251
  MIMQ_TOPIC_MESSAGE = 2022
252
+ #: 0.7.0: 저장 v4 엔진은 보내지 않는다(배치 read 응답은 2042). 구독은 받지 않고 ILTopicFrameMsg 의 해석만 남긴다
254
253
  MIMQ_TOPIC_BATCH_MESSAGE = 2024
255
- MIMQ_BATCH_PUBLISH_MESSAGE = 2025
256
- MIMQ_BATCH_PUBLISH_ACK_MESSAGE = 2026
254
+ #: 저장 v4 배치 read 응답(엔진 DESIGN-storv4 §3) = IL 프레임 하나. 본문(이진 BE) = u16 version=4, u16 partCount,
255
+ #: partCount x (i32 partition, i64 startOffset), i32 batchCount, v4 배치 x batchCount. 단건 read 는 그대로 2022 다.
256
+ MIMQ_TOPIC_BATCH_V4_MESSAGE = 2042
257
+ #: 발행 v2 봉투(엔진 DESIGN-pubv2). IL 헤더 + 이진 big-endian 본문(version, acks, flags, corrId, topicEpoch,
258
+ #: 토픽, 파티션 배치 N개 - 배치 = 저장 v4 배치, CRC-32).
259
+ MIMQ_TOPIC_PUBLISH_V2_MESSAGE = 2040
260
+ #: 발행 v2 ACK. corrId + 봉투 code / 사유 + 배치 수 + 배치마다 {partition, code, baseOffset, timestamp,
261
+ #: expectedSeq, 사유}. 봉투 헤더를 못 읽으면 봉투 code / 사유만 싣고 배치 수 0 이다.
262
+ MIMQ_TOPIC_PUBLISH_V2_ACK_MESSAGE = 2041
257
263
  MIMQ_LONG_REQUEST_CODE_LENGTH = 6
258
264
  MIMQ_LONG_REQUEST_ARG_LENGTH = 70
259
265
  MIMQ_LIST_ITEM_COUNT_LENGTH = 5
@@ -458,6 +464,26 @@ class ILC:
458
464
  MIMQ_TOPIC_REBALANCED = 4161
459
465
  MIMQ_OUT_OF_ORDER_SEQUENCE = 4162
460
466
  MIMQ_INVALID_PID = 4163
467
+ # 0.7.0 발행 v2 ACK(2041)의 봉투 / 배치 결과 code(엔진 DESIGN-pubv2 §8.1). 성공 4158, 기타 실패 4159,
468
+ # 순서 오류 4162(expectedSeq), PID 무효 4163 은 위 값을 그대로 쓴다. 실패 code 는 모두 "그 배치는 적재되지 않았다".
469
+ #: 멱등 중복 = 이미 적재됨(성공 계열). 링 밖이면 baseOffset -1
470
+ MIMQ_TOPIC_PUBLISH_DUPLICATE = 4164
471
+ #: FLUSH TOPIC 과 겹침. 비멱등은 재시도, 멱등은 실패
472
+ MIMQ_TOPIC_PUBLISH_FLUSHED = 4165
473
+ #: 토픽 epoch 가 바뀜 - TOPIC_METADATA 를 다시 받는다
474
+ MIMQ_TOPIC_PUBLISH_EPOCH_MISMATCH = 4166
475
+ #: 토픽 없음 또는 지우는 중
476
+ MIMQ_TOPIC_PUBLISH_NOT_FOUND = 4167
477
+ #: key / props 한도, 또는 v3 로 불린 배치가 세그먼트 크기 초과
478
+ MIMQ_TOPIC_PUBLISH_TOO_LARGE = 4168
479
+ #: 파티션 범위 / 중복, pid 섞임, 형식 오류, 컴팩트 토픽 키 없음
480
+ MIMQ_TOPIC_PUBLISH_INVALID_ARGUMENT = 4169
481
+ #: 저장 v4: 엔진이 늘 검사한다(flags bit0 무시)
482
+ MIMQ_TOPIC_PUBLISH_CRC_MISMATCH = 4170
483
+ #: 디스크 가드. 연결은 유지된다
484
+ MIMQ_TOPIC_PUBLISH_SUSPENDED = 4171
485
+ #: 사유에 leader=h:p
486
+ MIMQ_TOPIC_PUBLISH_FOLLOWER_NODE = 4172
461
487
 
462
488
  MIMQ_REMOVE_SESSION = 3309
463
489
  MIMQ_SET_CHANNEL_TIMEOUT = 3310
@@ -7,6 +7,7 @@ import struct
7
7
  import time
8
8
  from .common import ILC, ILRC
9
9
  from .exception import (
10
+ UnsupportedOperationException,
10
11
  ILMsgTypeException,
11
12
  ILOperationException,
12
13
  ILSessionException,
@@ -34,6 +35,29 @@ def _ba_pick_int(ba: bytes, offset: int, length: int) -> int:
34
35
  return 0
35
36
 
36
37
 
38
+
39
+ def clamp_max_frame_length(n: int) -> int:
40
+ """프레임 길이 상한 설정값을 엔진 설정 범위(1,048,576 ~ 999,999,999)로 맞춘다."""
41
+ return min(max(ILC.MIMQ_MIN_MAX_FRAME_LENGTH, int(n)), ILC.MIMQ_MAX_FRAME_LENGTH)
42
+
43
+
44
+ def _check_frame_limit(msg: ILMsg, limit: int) -> None:
45
+ """프레임 전체 길이(헤더 포함, 전송 구분자 제외)가 이 연결의 상한을 넘으면 **보내기 전에** 막는다.
46
+
47
+ 엔진은 넘는 프레임을 받으면 사유 없이 연결을 끊는다. 여기서는 송신 try 밖에서 던지므로 연결이 남는다.
48
+
49
+ Raises:
50
+ UnsupportedOperationException: 넘을 때. ``MIMQE_MSG_SIZE_OVER`` 로 시작한다.
51
+ """
52
+ content = msg.content
53
+ length = (content.get_msg_length() if content is not None else 0) + ILC.SZ_IL_HEADER
54
+ if length > limit:
55
+ raise UnsupportedOperationException(
56
+ "MIMQE_MSG_SIZE_OVER (frame length %d > max frame length %d - the engine drops the connection"
57
+ " over its Runtime@MaxFrameLength (default 128MiB); if the server allows more, raise it"
58
+ " with setMaxFrameLength)" % (length, limit))
59
+
60
+
37
61
  class TCPConnector:
38
62
  """블로킹 소켓 기반 **TCP 연결.** 메시지 단위로 보내고 받는다.
39
63
 
@@ -47,6 +71,8 @@ class TCPConnector:
47
71
 
48
72
  def __init__(self) -> None:
49
73
  self.socket: socket.socket | None = None
74
+ #: 이 연결로 보낼 프레임 길이 상한(바이트). 기본 128MiB(엔진 기본). :meth:`ILRequestHelper.set_max_frame_length`
75
+ self.max_frame_length = ILC.MIMQ_DEFAULT_MAX_FRAME_LENGTH
50
76
 
51
77
  def is_online(self) -> bool:
52
78
  """연결이 살아 있는지.
@@ -147,6 +173,7 @@ class TCPConnector:
147
173
  ILSessionException: 연결이 없거나 송신에 실패했을 때. **실패하면 소켓을
148
174
  닫는다** - 요청-응답 짝이 어긋난 채로 계속 쓰면 안 되기 때문이다.
149
175
  """
176
+ _check_frame_limit(msg, self.max_frame_length) # 송신 try 밖 - 넘어도 연결은 남는다
150
177
  self.send_bytes(msg.toByteArray(True))
151
178
 
152
179
  def send_bytes(self, buffer: bytes) -> None:
@@ -266,6 +293,8 @@ class TCPConnectorNIO:
266
293
  def __init__(self) -> None:
267
294
  self.sc: socket.socket | None = None
268
295
  self.selector: selectors.BaseSelector | None = None
296
+ #: 이 연결로 보낼 프레임 길이 상한(바이트). 기본 128MiB(엔진 기본).
297
+ self.max_frame_length = ILC.MIMQ_DEFAULT_MAX_FRAME_LENGTH
269
298
 
270
299
  def is_online(self) -> bool:
271
300
  """연결이 살아 있는지.
@@ -366,6 +395,7 @@ class TCPConnectorNIO:
366
395
  ILSessionException: 연결이 없거나 송신에 실패했을 때. **실패하면 소켓을
367
396
  닫는다** - 요청-응답 짝이 어긋난 채로 계속 쓰면 안 되기 때문이다.
368
397
  """
398
+ _check_frame_limit(msg, self.max_frame_length) # 송신 try 밖 - 넘어도 연결은 남는다
369
399
  self.send_bytes(msg.toByteArray(True))
370
400
 
371
401
  def send_bytes(self, buffer: bytes) -> None:
@@ -602,6 +632,9 @@ class ILRequestHelper:
602
632
  def __init__(self, conn: TCPConnector | None = None) -> None:
603
633
  self.conn = conn or TCPConnector()
604
634
  self.sessionTimeoutMS = 600000
635
+ #: 프레임 길이 상한(바이트). 커넥터를 갈아 끼워도 따라간다(:meth:`set_conn`).
636
+ self.maxFrameLength = ILC.MIMQ_DEFAULT_MAX_FRAME_LENGTH
637
+ self.conn.max_frame_length = self.maxFrameLength
605
638
 
606
639
  def connect(self, host: str, port: int) -> None:
607
640
  """큐 관리자/서비스에 연결한다.
@@ -627,9 +660,11 @@ class ILRequestHelper:
627
660
  conn (TCPConnector): 새 커넥터.
628
661
 
629
662
  Note:
630
- 재접속 후 같은 헬퍼를 계속 쓸 때 필요하다.
663
+ 재접속 후 같은 헬퍼를 계속 쓸 때 필요하다. 프레임 길이 상한은 새 커넥터로 옮겨 간다.
631
664
  """
632
665
  self.conn = conn
666
+ if conn is not None:
667
+ conn.max_frame_length = self.maxFrameLength
633
668
 
634
669
  def setConn(self, conn: TCPConnector) -> None:
635
670
  """:meth:`set_conn` 의 camelCase 별칭.
@@ -694,6 +729,31 @@ class ILRequestHelper:
694
729
  """
695
730
  self.sessionTimeoutMS = value
696
731
 
732
+ def set_max_frame_length(self, value: int) -> None:
733
+ """이 연결로 보낼 프레임 길이 상한(바이트)을 정한다. 기본 134217728(128MiB).
734
+
735
+ Args:
736
+ value (int): 상한. 1048576 ~ 999999999 로 보정된다(엔진 ``Runtime@MaxFrameLength`` 범위).
737
+ """
738
+ self.maxFrameLength = clamp_max_frame_length(value)
739
+ self.conn.max_frame_length = self.maxFrameLength
740
+
741
+ def setMaxFrameLength(self, value: int) -> None:
742
+ """:meth:`set_max_frame_length` 의 camelCase 별칭."""
743
+ self.set_max_frame_length(value)
744
+
745
+ def get_max_frame_length(self) -> int:
746
+ """이 연결의 프레임 길이 상한(바이트). 기본 134217728(128MiB).
747
+
748
+ Returns:
749
+ int: 상한(바이트).
750
+ """
751
+ return self.maxFrameLength
752
+
753
+ def getMaxFrameLength(self) -> int:
754
+ """:meth:`get_max_frame_length` 의 camelCase 별칭."""
755
+ return self.get_max_frame_length()
756
+
697
757
  def setSessionTimeout(self, value: int) -> None:
698
758
  """:meth:`set_session_timeout` 의 camelCase 별칭.
699
759
 
@@ -213,7 +213,7 @@ class ILMsgTypeException(Exception):
213
213
  type_code (int): 실제로 받은 메시지 종류. **정수 상수**다 -
214
214
  :data:`ILC.MIMQ_DATA_MESSAGE` (2000) /
215
215
  :data:`ILC.MIMQ_REPORT_MESSAGE` (2002) /
216
- :data:`ILC.MIMQ_PUBLISH_MESSAGE` (2020) 등 ``MIMQ_*_MESSAGE`` 계열.
216
+ :data:`ILC.MIMQ_TOPIC_MESSAGE` (2022) 등 ``MIMQ_*_MESSAGE`` 계열.
217
217
  msg (str): 오류 메시지.
218
218
  """
219
219
  super().__init__(msg)