jetstream-api 0.7.2__tar.gz → 0.7.4__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 (26) hide show
  1. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/PKG-INFO +89 -1
  2. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/README.md +88 -0
  3. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/__init__.py +1 -1
  4. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/admin.py +6 -3
  5. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/admintopic.py +22 -4
  6. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/common.py +12 -0
  7. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/connectivity.py +556 -47
  8. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/msg.py +300 -33
  9. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/pattern.py +407 -76
  10. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/producer.py +93 -59
  11. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/properties.py +5 -4
  12. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/qmgr.py +162 -20
  13. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/topic.py +574 -140
  14. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/jetstream_api.egg-info/PKG-INFO +89 -1
  15. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/pyproject.toml +1 -1
  16. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/LICENSE.txt +0 -0
  17. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/exception.py +0 -0
  18. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/partitioner.py +0 -0
  19. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/py.typed +0 -0
  20. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/ilink/util.py +0 -0
  21. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/jetstream_api.egg-info/SOURCES.txt +0 -0
  22. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/jetstream_api.egg-info/dependency_links.txt +0 -0
  23. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/jetstream_api.egg-info/requires.txt +0 -0
  24. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/jetstream_api.egg-info/top_level.txt +0 -0
  25. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/setup.cfg +0 -0
  26. {jetstream_api-0.7.2 → jetstream_api-0.7.4}/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.2
3
+ Version: 0.7.4
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,94 @@ Python으로 작성된 ILink 클라이언트 API입니다.
54
54
  ## 설치 방법
55
55
  pip install jetstream-api
56
56
 
57
+ ## 0.7.4 변경 - 패턴 토픽 떼어 내기 = 그룹 탈퇴(LEAVE), seek 규칙 (자바 v2.3.5 와 같음)
58
+
59
+ - **엔진 요구**: 아래 새 동작은 **엔진 7.0.1.3417 이상**(CONSGRP-1)에서 나옵니다. 그보다 옛 엔진에서는 그룹 탈퇴 요청을 조용히
60
+ 건너뛰고(예전처럼 패턴 구독을 닫을 때까지 멤버로 남습니다), 파티션을 생략한 `seek_to_offset()` 은 0번 파티션을 되감습니다.
61
+ 클라이언트는 엔진 판을 따로 검사하지 않습니다.
62
+ - **패턴 구독 - 떼어 낸 토픽은 그룹에서 빠집니다**: 재평가로 더 이상 매칭되지 않거나 read 중 오류로 떼어 낸 토픽은 그 (토픽, 구독)
63
+ 그룹에서 이 세션을 뺍니다(`MIMQ_TOPIC_LEAVE` = 3401). 엔진이 곧바로 재배정하므로 같은 구독 이름의 다른 멤버가 그 파티션을
64
+ 이어받고, 커서는 남습니다. 예전에는 연결을 닫지 않으니 패턴 구독을 닫을 때까지 멤버로 남아 그 파티션을 아무도 읽지 않았습니다.
65
+ 그 토픽이 실린 다중 토픽 read 가 엔진에 걸려 있는 동안은 보내지 않고(엔진이 `MIMQE_READ_IN_PROGRESS` 로 거절합니다) 그 read 가
66
+ 돌아온 직후 / 다음 read 전에 보냅니다 - 다른 스레드의 `refresh_now()` 는 걸린 read 를 기다리지 않고 돌아옵니다. best-effort 라
67
+ 실패(구독 없음, 옛 엔진, 통신 실패)는 앱에 올리지 않습니다. 패턴 `close()` / `unsubscribe()` 와 단일 토픽 구독의 `close()` 는
68
+ 그대로입니다(연결을 닫으면 엔진이 멤버를 뺍니다).
69
+ - **seek 는 이 멤버에게 배정된 파티션만**: 배정 밖 파티션이면 `ILOperationException`
70
+ (`MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)`) 입니다. 재배정 통지가 아니라 거절이라 받아 둔 레코드는
71
+ 그대로이고 다시 보내지 않습니다. 아직 read 하지 않은 구독은 그룹에 멤버가 하나도 없을 때만 seek 할 수 있습니다 - "subscribe →
72
+ seek → read" 는 혼자일 때만 되고, 다른 멤버가 있으면 첫 read 로 배정 통지를 받은 뒤(예: `set_rebalance_listener()` 콜백 안)
73
+ seek 하세요. `assign()` 구독은 배정 목록 밖 파티션이면 `(Partition : N not in manual assignment)` 사유로 거절됩니다. 관리 연결
74
+ (`ILAdminTopicSubscription`)의 seek 는 그룹에 멤버가 없을 때만 됩니다.
75
+ - **`seek_to_offset(offset)` - 파티션 생략은 엔진이 정합니다**: 0.7.3 은 생략하면 `;partition:0` 을 붙였습니다. 이제 `offset:N`
76
+ 그대로 보냅니다. 새 엔진은 파티션이 하나인 토픽에서만 받고, 다중 파티션 토픽이면 `MIMQE_INVALID_ARGUMENT (Partition : REQUIRED,
77
+ Available : 0~N)` 로 거절합니다 - **다중 파티션 토픽에서는 `partition` 을 주세요.** 옛 엔진은 0번 파티션을 되감습니다. 성공하면
78
+ 받아 둔 레코드는 0번 파티션 것만 버리고, 거절되면 아무것도 버리지 않습니다.
79
+ - **`seek_to_time(epoch_ms, partition=-1)`** (`seekToTime(epochMs, partition)`): 파티션을 줄 수 있습니다(`time:T;partition:P`,
80
+ 받아 둔 레코드도 P 것만 버립니다). 새 엔진은 이 멤버에게 배정된 파티션만 옮기고(파티션을 주면 그 파티션만), 돌려주는 값은 옮긴
81
+ 파티션 중 번호가 가장 작은 파티션의 결과 오프셋입니다(옮긴 것이 없으면 -1). 옛 엔진은 파티션을 무시하고 구독 전체를 옮기며
82
+ 0번 파티션의 결과를 줍니다 - 옛 엔진에서는 파티션을 주지 마세요.
83
+ - **문서**: `ILAdminTopicSubscription.seek_to_offset()` 의 "`-1` 이면 전 파티션" 은 틀린 설명이었습니다(엔진은 0번 파티션만
84
+ 되감았습니다).
85
+ - **NIO 커넥터(`TCPConnectorNIO`)도 송수신이 실패하면 연결을 닫습니다**(기본 커넥터와 같게): 전에는 큰 프레임을 받다가 시간이 다하면
86
+ 나머지가 소켓에 남은 채 연결이 살아 있어 같은 연결의 다음 요청이 그 조각부터 읽었고(`invalid transmission delimiter`), 자동 확인
87
+ GET 이면 그 메시지를 잃었습니다. 상대가 프레임 중간에 끊으면 끝없이 돌던 것도 곧바로 `MIMQE_NIO_ERROR` 로 끝나고, 기한이 지났어도
88
+ 소켓에 다 와 있는 프레임은 돌려주며, 보낼 자리가 없을 때(`BlockingIOError`) 곧바로 실패하던 송신은 기다렸다 이어 보냅니다. 기본
89
+ (블로킹) 커넥터에서도 프레임 중간에 시간이 다한 자동 확인 GET 의 메시지는 잃습니다(최대 한 번 전달) - 아주 큰 메시지는 GET 대기
90
+ 시간 / 세션 시간 초과를 넉넉히 주거나 트랜잭션 GET(`auto_commit=False` 세션 + `commit()`)을 쓰세요.
91
+
92
+ ## 0.7.3 변경 - 구독마다 전용 연결, 소비 프로토콜 v5, 발행 / 소비 CPU 절감 (자바 v2.3.4 와 같음)
93
+
94
+ - **엔진 요구**: 토픽 구독은 **엔진 7.0.1.3415 이상**(소비 프로토콜 v5)에서만 동작합니다. 그보다 옛 엔진은 요청 태그를
95
+ 모르므로 구독 연결을 끊습니다. 발행과 큐 기능은 영향이 없습니다.
96
+ - **구독마다 전용 연결**: 구독(패턴 구독이면 패턴 하나)마다 엔진 세션을 하나 따로 엽니다(클라이언트 이름 `<이름>-SUB`).
97
+ 한 구독이 레코드를 기다려도 다른 구독이나 같은 `ILQmgr` 의 큐 / 관리 요청이 기다리지 않습니다. 대신 큐 관리자의 세션
98
+ 수가 구독 수만큼 늘어납니다. 구독을 `close()` 하면 그 세션이 닫히고(엔진은 곧바로 멤버를 빼고 재배정합니다),
99
+ `ILQmgr.disconnect()` 는 그 세션에서 만든 구독을 모두 닫으며, `reconnect()` 는 구독 세션도 함께 다시 붙입니다.
100
+ `close()` 뒤의 read / commit 은 오류입니다. 다른 스레드의 read 가 레코드를 기다리던 중에 `close()` 하면 그 read 도 곧바로
101
+ 끝납니다.
102
+ - **`close()` 와 `unsubscribe()` 의 차이**: `close()` 는 **이 멤버만** 빠집니다(연결을 끊으면 엔진이 곧바로 멤버를 빼고 재배정하며,
103
+ 커서는 남습니다). `unsubscribe()` 는 **구독(그룹) 자체를 지웁니다** - 모든 파티션의 커서(durable 포함)가 사라지고, 같은
104
+ 구독 이름으로 붙어 있는 다른 멤버(다른 프로세스 포함)도 다음 read 에서 `MIMQE_TOPIC_SUBSCRIPTION_NOT_FOUND` 를 받습니다.
105
+ 구독을 없앨 때만 부르세요. `unsubscribe()` 는 `close()` 뒤에도 됩니다(`ILQmgr` 의 연결로 보냅니다).
106
+ 구독 세션은 `ILQmgr` 의 세션 모드와 상관없이 **늘 자동 확인 모드**로 열립니다 - 토픽 read / commit / seek 는 원래 세션
107
+ 트랜잭션에 묶이지 않으므로(`qmgr.commit()` / `rollback()` 은 큐만 다룹니다) 동작은 그대로입니다.
108
+ - **소비 프로토콜 v5**: 구독 연결의 요청에 태그를 붙여 겹쳐 보냅니다 - 레코드를 기다리는 동안에도 같은 구독의
109
+ commit / seek 가 곧바로 처리됩니다(한 구독의 read 는 하나씩 나갑니다). 패턴 구독은 매칭된 토픽 전체를 요청 한 번(다중 토픽
110
+ read)으로 받습니다. 커밋 좌표는 파티션 수와 관계없이 **한 요청**에 실리고, 한 요청 전체가 되거나 안 되거나입니다 - 이 멤버에게
111
+ 배정되지 않은 파티션이 하나라도 있으면 `MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)` 로 아무것도
112
+ 커밋되지 않습니다.
113
+
114
+ - **정확성**:
115
+ - 한 `ILQmgr` 에서 `listen()` 구독이 둘 이상이거나, `listen()` 중에 같은 `ILQmgr` 로 다른 요청(큐 put/get, 관리 요청,
116
+ 다른 구독의 read)을 하면 소켓 하나에서 요청과 응답이 섞일 수 있었습니다. 이제 연결마다 잠금 하나로 요청-응답을 짝 단위로
117
+ 줄 세웁니다(먼저 기다린 쪽이 먼저).
118
+ - `listen()` 콜백이 예외를 던져 멈추면, 뒤이은 `close()` 가 처리하지 않은 레코드까지 AUTO 커밋하던 결함을 고쳤습니다.
119
+ 이제 콜백이 정상으로 끝난 레코드만 커밋 대상이고, 콜백하지 못한 레코드는 같은 핸들의 다음 read / listen 이 다시 줍니다.
120
+ - `seek_to_offset(offset, partition)` 이 **다른 파티션**의 받아 두기만 한 레코드와 AUTO 커밋 좌표까지 버리던 결함을
121
+ 고쳤습니다(0.7.2 에도 있었습니다). 엔진은 지정한 파티션만 되감으므로 버려진 다른 파티션 구간은 다시 오지 않았고, AUTO 는
122
+ 뒤 커밋이 그 구간을 건너뛰었습니다. 이제 지정한 파티션만 버리고, seek 응답보다 먼저 도착한(곧 seek 전 위치에서 읽은) 그
123
+ 파티션의 레코드는 앱에 주지 않습니다. `partition` 을 생략하면 엔진은 **0번 파티션 하나만** 되감습니다(`offset=0` 이어도
124
+ 구독 전체가 아닙니다). 예전에는 이때도 모든 파티션의 받아 둔 레코드를 버렸습니다 - 이제 `;partition:0` 을 붙여 보내고 0번
125
+ 것만 버립니다. `seek_to_time()` 은 전 파티션을 되감으므로 전부 버립니다.
126
+ - seek 는 엔진이 멤버를 보지 않고 커서를 옮깁니다. 이 멤버에게 배정된 파티션만 되감고(`get_assignment()`), `seek_to_time()` 은
127
+ 그룹에 멤버가 하나일 때만 쓰세요.
128
+ - `close()` 의 AUTO 커밋이 `MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)` 로 거절되면 그 파티션을 빼고
129
+ 다시 보냅니다 - 아직 소유한 파티션의 좌표는 반영됩니다(예전에는 전부 버려져 다음 멤버에게 다시 갔습니다).
130
+ - **문서**: 세션 목록(`ILSessionProperty`)의 `destroyedTime` 은 살아 있는 세션에서 **-1** 입니다(0 이 아닙니다). 끊긴 세션은
131
+ 끊긴 시각(epoch 밀리초)입니다. 살아 있는지는 `status` 로 판정하세요.
132
+ - **발행 CPU**: `send()` 가 레코드를 배치 버퍼에 곧바로 붙이고, 잠금 / 파티션 선택 / 키 인코딩 비용을 줄였습니다(레코드당 약 30% 감소).
133
+ - **소비 CPU**: 저장 v4 배치 응답을 풀면서 레코드 객체를 바로 만들고, AUTO 커밋 병합을 파티션마다 한 번 합니다(`read_batch` 약 40% 감소).
134
+ - **소비 왕복**:
135
+ - `read()` 가 AUTO / MANUAL 에서 prefetch 건수(기본 500)까지 한 번에 받아 두고 차례로 줍니다(레코드마다 왕복하지 않습니다).
136
+ IMMEDIATE 는 지금처럼 1건씩 요청합니다. 받아 두기만 한 레코드는 커밋되지 않으며, AUTO 커밋은 다음 요청(또는 `close()`)에 실립니다.
137
+ - `commit(list)` / `close()` 가 파티션마다 가장 큰 오프셋을 모아 파티션 수와 관계없이 한 요청으로 커밋합니다.
138
+ - `listen()` 과 prefetch 기본 배치가 100 에서 500 이 됐습니다.
139
+ - **패턴 구독**: `read` / `read_batch` 한 번이 매칭된 토픽 전체를 다중 토픽 read 요청 하나로 묻습니다. 한 토픽이라도 레코드가
140
+ 있으면 곧바로 돌아오고, 모두 비었으면 `timeout_ms` 동안 어느 토픽이든 들어오기를 기다립니다 - 한가한 토픽이 많아도 바쁜 토픽이
141
+ 늦어지지 않습니다. 한 번에 토픽 하나가 가져가는 양은 `bufferPerTopic` 으로 묶고, 시작 토픽을 호출마다 한 칸씩 돌립니다(바쁜
142
+ 토픽이 셋 이상일 때 하나를 건너뛰던 결함을 고쳤습니다). 목록 `commit` 은 토픽마다 한 요청이며, 보내기 전에 전부 검사합니다
143
+ (출처가 없는 레코드가 있으면 아무것도 커밋하지 않습니다).
144
+
57
145
  ## 0.7.2 변경 - batchSize 기본값 262144 로 되돌림, 발행 경로 CPU 절감 (자바 v2.3.2 와 같음)
58
146
 
59
147
  - `batchSize` 기본값을 **262144** 로 되돌렸습니다. 0.7.1 의 16384 에서 파이썬 클라는 처리량이 24~35% 낮았습니다(GIL 이
@@ -8,6 +8,94 @@ Python으로 작성된 ILink 클라이언트 API입니다.
8
8
  ## 설치 방법
9
9
  pip install jetstream-api
10
10
 
11
+ ## 0.7.4 변경 - 패턴 토픽 떼어 내기 = 그룹 탈퇴(LEAVE), seek 규칙 (자바 v2.3.5 와 같음)
12
+
13
+ - **엔진 요구**: 아래 새 동작은 **엔진 7.0.1.3417 이상**(CONSGRP-1)에서 나옵니다. 그보다 옛 엔진에서는 그룹 탈퇴 요청을 조용히
14
+ 건너뛰고(예전처럼 패턴 구독을 닫을 때까지 멤버로 남습니다), 파티션을 생략한 `seek_to_offset()` 은 0번 파티션을 되감습니다.
15
+ 클라이언트는 엔진 판을 따로 검사하지 않습니다.
16
+ - **패턴 구독 - 떼어 낸 토픽은 그룹에서 빠집니다**: 재평가로 더 이상 매칭되지 않거나 read 중 오류로 떼어 낸 토픽은 그 (토픽, 구독)
17
+ 그룹에서 이 세션을 뺍니다(`MIMQ_TOPIC_LEAVE` = 3401). 엔진이 곧바로 재배정하므로 같은 구독 이름의 다른 멤버가 그 파티션을
18
+ 이어받고, 커서는 남습니다. 예전에는 연결을 닫지 않으니 패턴 구독을 닫을 때까지 멤버로 남아 그 파티션을 아무도 읽지 않았습니다.
19
+ 그 토픽이 실린 다중 토픽 read 가 엔진에 걸려 있는 동안은 보내지 않고(엔진이 `MIMQE_READ_IN_PROGRESS` 로 거절합니다) 그 read 가
20
+ 돌아온 직후 / 다음 read 전에 보냅니다 - 다른 스레드의 `refresh_now()` 는 걸린 read 를 기다리지 않고 돌아옵니다. best-effort 라
21
+ 실패(구독 없음, 옛 엔진, 통신 실패)는 앱에 올리지 않습니다. 패턴 `close()` / `unsubscribe()` 와 단일 토픽 구독의 `close()` 는
22
+ 그대로입니다(연결을 닫으면 엔진이 멤버를 뺍니다).
23
+ - **seek 는 이 멤버에게 배정된 파티션만**: 배정 밖 파티션이면 `ILOperationException`
24
+ (`MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)`) 입니다. 재배정 통지가 아니라 거절이라 받아 둔 레코드는
25
+ 그대로이고 다시 보내지 않습니다. 아직 read 하지 않은 구독은 그룹에 멤버가 하나도 없을 때만 seek 할 수 있습니다 - "subscribe →
26
+ seek → read" 는 혼자일 때만 되고, 다른 멤버가 있으면 첫 read 로 배정 통지를 받은 뒤(예: `set_rebalance_listener()` 콜백 안)
27
+ seek 하세요. `assign()` 구독은 배정 목록 밖 파티션이면 `(Partition : N not in manual assignment)` 사유로 거절됩니다. 관리 연결
28
+ (`ILAdminTopicSubscription`)의 seek 는 그룹에 멤버가 없을 때만 됩니다.
29
+ - **`seek_to_offset(offset)` - 파티션 생략은 엔진이 정합니다**: 0.7.3 은 생략하면 `;partition:0` 을 붙였습니다. 이제 `offset:N`
30
+ 그대로 보냅니다. 새 엔진은 파티션이 하나인 토픽에서만 받고, 다중 파티션 토픽이면 `MIMQE_INVALID_ARGUMENT (Partition : REQUIRED,
31
+ Available : 0~N)` 로 거절합니다 - **다중 파티션 토픽에서는 `partition` 을 주세요.** 옛 엔진은 0번 파티션을 되감습니다. 성공하면
32
+ 받아 둔 레코드는 0번 파티션 것만 버리고, 거절되면 아무것도 버리지 않습니다.
33
+ - **`seek_to_time(epoch_ms, partition=-1)`** (`seekToTime(epochMs, partition)`): 파티션을 줄 수 있습니다(`time:T;partition:P`,
34
+ 받아 둔 레코드도 P 것만 버립니다). 새 엔진은 이 멤버에게 배정된 파티션만 옮기고(파티션을 주면 그 파티션만), 돌려주는 값은 옮긴
35
+ 파티션 중 번호가 가장 작은 파티션의 결과 오프셋입니다(옮긴 것이 없으면 -1). 옛 엔진은 파티션을 무시하고 구독 전체를 옮기며
36
+ 0번 파티션의 결과를 줍니다 - 옛 엔진에서는 파티션을 주지 마세요.
37
+ - **문서**: `ILAdminTopicSubscription.seek_to_offset()` 의 "`-1` 이면 전 파티션" 은 틀린 설명이었습니다(엔진은 0번 파티션만
38
+ 되감았습니다).
39
+ - **NIO 커넥터(`TCPConnectorNIO`)도 송수신이 실패하면 연결을 닫습니다**(기본 커넥터와 같게): 전에는 큰 프레임을 받다가 시간이 다하면
40
+ 나머지가 소켓에 남은 채 연결이 살아 있어 같은 연결의 다음 요청이 그 조각부터 읽었고(`invalid transmission delimiter`), 자동 확인
41
+ GET 이면 그 메시지를 잃었습니다. 상대가 프레임 중간에 끊으면 끝없이 돌던 것도 곧바로 `MIMQE_NIO_ERROR` 로 끝나고, 기한이 지났어도
42
+ 소켓에 다 와 있는 프레임은 돌려주며, 보낼 자리가 없을 때(`BlockingIOError`) 곧바로 실패하던 송신은 기다렸다 이어 보냅니다. 기본
43
+ (블로킹) 커넥터에서도 프레임 중간에 시간이 다한 자동 확인 GET 의 메시지는 잃습니다(최대 한 번 전달) - 아주 큰 메시지는 GET 대기
44
+ 시간 / 세션 시간 초과를 넉넉히 주거나 트랜잭션 GET(`auto_commit=False` 세션 + `commit()`)을 쓰세요.
45
+
46
+ ## 0.7.3 변경 - 구독마다 전용 연결, 소비 프로토콜 v5, 발행 / 소비 CPU 절감 (자바 v2.3.4 와 같음)
47
+
48
+ - **엔진 요구**: 토픽 구독은 **엔진 7.0.1.3415 이상**(소비 프로토콜 v5)에서만 동작합니다. 그보다 옛 엔진은 요청 태그를
49
+ 모르므로 구독 연결을 끊습니다. 발행과 큐 기능은 영향이 없습니다.
50
+ - **구독마다 전용 연결**: 구독(패턴 구독이면 패턴 하나)마다 엔진 세션을 하나 따로 엽니다(클라이언트 이름 `<이름>-SUB`).
51
+ 한 구독이 레코드를 기다려도 다른 구독이나 같은 `ILQmgr` 의 큐 / 관리 요청이 기다리지 않습니다. 대신 큐 관리자의 세션
52
+ 수가 구독 수만큼 늘어납니다. 구독을 `close()` 하면 그 세션이 닫히고(엔진은 곧바로 멤버를 빼고 재배정합니다),
53
+ `ILQmgr.disconnect()` 는 그 세션에서 만든 구독을 모두 닫으며, `reconnect()` 는 구독 세션도 함께 다시 붙입니다.
54
+ `close()` 뒤의 read / commit 은 오류입니다. 다른 스레드의 read 가 레코드를 기다리던 중에 `close()` 하면 그 read 도 곧바로
55
+ 끝납니다.
56
+ - **`close()` 와 `unsubscribe()` 의 차이**: `close()` 는 **이 멤버만** 빠집니다(연결을 끊으면 엔진이 곧바로 멤버를 빼고 재배정하며,
57
+ 커서는 남습니다). `unsubscribe()` 는 **구독(그룹) 자체를 지웁니다** - 모든 파티션의 커서(durable 포함)가 사라지고, 같은
58
+ 구독 이름으로 붙어 있는 다른 멤버(다른 프로세스 포함)도 다음 read 에서 `MIMQE_TOPIC_SUBSCRIPTION_NOT_FOUND` 를 받습니다.
59
+ 구독을 없앨 때만 부르세요. `unsubscribe()` 는 `close()` 뒤에도 됩니다(`ILQmgr` 의 연결로 보냅니다).
60
+ 구독 세션은 `ILQmgr` 의 세션 모드와 상관없이 **늘 자동 확인 모드**로 열립니다 - 토픽 read / commit / seek 는 원래 세션
61
+ 트랜잭션에 묶이지 않으므로(`qmgr.commit()` / `rollback()` 은 큐만 다룹니다) 동작은 그대로입니다.
62
+ - **소비 프로토콜 v5**: 구독 연결의 요청에 태그를 붙여 겹쳐 보냅니다 - 레코드를 기다리는 동안에도 같은 구독의
63
+ commit / seek 가 곧바로 처리됩니다(한 구독의 read 는 하나씩 나갑니다). 패턴 구독은 매칭된 토픽 전체를 요청 한 번(다중 토픽
64
+ read)으로 받습니다. 커밋 좌표는 파티션 수와 관계없이 **한 요청**에 실리고, 한 요청 전체가 되거나 안 되거나입니다 - 이 멤버에게
65
+ 배정되지 않은 파티션이 하나라도 있으면 `MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)` 로 아무것도
66
+ 커밋되지 않습니다.
67
+
68
+ - **정확성**:
69
+ - 한 `ILQmgr` 에서 `listen()` 구독이 둘 이상이거나, `listen()` 중에 같은 `ILQmgr` 로 다른 요청(큐 put/get, 관리 요청,
70
+ 다른 구독의 read)을 하면 소켓 하나에서 요청과 응답이 섞일 수 있었습니다. 이제 연결마다 잠금 하나로 요청-응답을 짝 단위로
71
+ 줄 세웁니다(먼저 기다린 쪽이 먼저).
72
+ - `listen()` 콜백이 예외를 던져 멈추면, 뒤이은 `close()` 가 처리하지 않은 레코드까지 AUTO 커밋하던 결함을 고쳤습니다.
73
+ 이제 콜백이 정상으로 끝난 레코드만 커밋 대상이고, 콜백하지 못한 레코드는 같은 핸들의 다음 read / listen 이 다시 줍니다.
74
+ - `seek_to_offset(offset, partition)` 이 **다른 파티션**의 받아 두기만 한 레코드와 AUTO 커밋 좌표까지 버리던 결함을
75
+ 고쳤습니다(0.7.2 에도 있었습니다). 엔진은 지정한 파티션만 되감으므로 버려진 다른 파티션 구간은 다시 오지 않았고, AUTO 는
76
+ 뒤 커밋이 그 구간을 건너뛰었습니다. 이제 지정한 파티션만 버리고, seek 응답보다 먼저 도착한(곧 seek 전 위치에서 읽은) 그
77
+ 파티션의 레코드는 앱에 주지 않습니다. `partition` 을 생략하면 엔진은 **0번 파티션 하나만** 되감습니다(`offset=0` 이어도
78
+ 구독 전체가 아닙니다). 예전에는 이때도 모든 파티션의 받아 둔 레코드를 버렸습니다 - 이제 `;partition:0` 을 붙여 보내고 0번
79
+ 것만 버립니다. `seek_to_time()` 은 전 파티션을 되감으므로 전부 버립니다.
80
+ - seek 는 엔진이 멤버를 보지 않고 커서를 옮깁니다. 이 멤버에게 배정된 파티션만 되감고(`get_assignment()`), `seek_to_time()` 은
81
+ 그룹에 멤버가 하나일 때만 쓰세요.
82
+ - `close()` 의 AUTO 커밋이 `MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)` 로 거절되면 그 파티션을 빼고
83
+ 다시 보냅니다 - 아직 소유한 파티션의 좌표는 반영됩니다(예전에는 전부 버려져 다음 멤버에게 다시 갔습니다).
84
+ - **문서**: 세션 목록(`ILSessionProperty`)의 `destroyedTime` 은 살아 있는 세션에서 **-1** 입니다(0 이 아닙니다). 끊긴 세션은
85
+ 끊긴 시각(epoch 밀리초)입니다. 살아 있는지는 `status` 로 판정하세요.
86
+ - **발행 CPU**: `send()` 가 레코드를 배치 버퍼에 곧바로 붙이고, 잠금 / 파티션 선택 / 키 인코딩 비용을 줄였습니다(레코드당 약 30% 감소).
87
+ - **소비 CPU**: 저장 v4 배치 응답을 풀면서 레코드 객체를 바로 만들고, AUTO 커밋 병합을 파티션마다 한 번 합니다(`read_batch` 약 40% 감소).
88
+ - **소비 왕복**:
89
+ - `read()` 가 AUTO / MANUAL 에서 prefetch 건수(기본 500)까지 한 번에 받아 두고 차례로 줍니다(레코드마다 왕복하지 않습니다).
90
+ IMMEDIATE 는 지금처럼 1건씩 요청합니다. 받아 두기만 한 레코드는 커밋되지 않으며, AUTO 커밋은 다음 요청(또는 `close()`)에 실립니다.
91
+ - `commit(list)` / `close()` 가 파티션마다 가장 큰 오프셋을 모아 파티션 수와 관계없이 한 요청으로 커밋합니다.
92
+ - `listen()` 과 prefetch 기본 배치가 100 에서 500 이 됐습니다.
93
+ - **패턴 구독**: `read` / `read_batch` 한 번이 매칭된 토픽 전체를 다중 토픽 read 요청 하나로 묻습니다. 한 토픽이라도 레코드가
94
+ 있으면 곧바로 돌아오고, 모두 비었으면 `timeout_ms` 동안 어느 토픽이든 들어오기를 기다립니다 - 한가한 토픽이 많아도 바쁜 토픽이
95
+ 늦어지지 않습니다. 한 번에 토픽 하나가 가져가는 양은 `bufferPerTopic` 으로 묶고, 시작 토픽을 호출마다 한 칸씩 돌립니다(바쁜
96
+ 토픽이 셋 이상일 때 하나를 건너뛰던 결함을 고쳤습니다). 목록 `commit` 은 토픽마다 한 요청이며, 보내기 전에 전부 검사합니다
97
+ (출처가 없는 레코드가 있으면 아무것도 커밋하지 않습니다).
98
+
11
99
  ## 0.7.2 변경 - batchSize 기본값 262144 로 되돌림, 발행 경로 CPU 절감 (자바 v2.3.2 와 같음)
12
100
 
13
101
  - `batchSize` 기본값을 **262144** 로 되돌렸습니다. 0.7.1 의 16384 에서 파이썬 클라는 처리량이 24~35% 낮았습니다(GIL 이
@@ -1,6 +1,6 @@
1
1
  """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
2
 
3
- __version__ = "0.7.2"
3
+ __version__ = "0.7.4"
4
4
 
5
5
  # --- Admin / Service ---
6
6
  from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser
@@ -6,7 +6,7 @@ import time
6
6
  import uuid
7
7
 
8
8
  from .common import ILC, ILRC, MatchOption
9
- from .connectivity import ILRequestHelper
9
+ from .connectivity import ILRequestHelper, _io_lock_of
10
10
  from .exception import (
11
11
  UnsupportedOperationException,
12
12
  ILException,
@@ -5250,7 +5250,6 @@ class ILAdminQmgr:
5250
5250
  str(self.waitMsgTimeoutMS),
5251
5251
  )
5252
5252
  while True:
5253
- self.service.getConn().send(req_msg)
5254
5253
  timeout = self.service.getSessionTimeout()
5255
5254
  if (
5256
5255
  not self.isInfiniteWaiting
@@ -5259,7 +5258,11 @@ class ILAdminQmgr:
5259
5258
  ):
5260
5259
  timeout = self.waitMsgTimeoutMS * 2
5261
5260
 
5262
- msg = self.service.getConn().recv(timeout)
5261
+ # 0.7.3: 요청-응답 짝을 연결 잠금 안에서(무한 대기면 회차마다 놓는다)
5262
+ conn = self.service.getConn()
5263
+ with _io_lock_of(conn):
5264
+ conn.send(req_msg)
5265
+ msg = conn.recv(timeout)
5263
5266
  if msg.getType() == ILC.MIMQ_DATA_MESSAGE:
5264
5267
  return msg.getDataMsg()
5265
5268
 
@@ -103,6 +103,12 @@ class ILAdminTopicSubscription:
103
103
  삼키고 같은 조건으로 한 번 더 요청하므로 호출부는 신경 쓸 것이 없다.
104
104
  배정 내역을 보고 싶으면 :meth:`get_last_assignment`.
105
105
 
106
+ .. note:: seek 는 빈 그룹에서만 (엔진 7.0.1.3417 이상)
107
+
108
+ 관리 연결의 :meth:`seek_to_offset` / :meth:`seek_to_time` 은 그 구독(그룹)에 멤버가 하나도 없을 때만 된다.
109
+ 데이터 연결로 읽는 멤버가 있으면 ``MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)`` 로
110
+ 거절된다 - 그때는 그 멤버가 자기 구독 핸들로 seek 한다.
111
+
106
112
  Example::
107
113
 
108
114
  from ilink.exception import ILNoMsgException
@@ -308,11 +314,18 @@ class ILAdminTopicSubscription:
308
314
 
309
315
  Args:
310
316
  offset (int): 다음에 읽을 오프셋.
311
- partition (int): 대상 파티션. ``-1``(기본)이면 전 파티션.
317
+ partition (int): 대상 파티션. ``-1`` (기본)이면 생략 - 파티션 없이 ``offset:N`` 으로 보내고 판단은 엔진에
318
+ 맡긴다. **전 파티션이 아니다.** 다중 파티션 토픽에서는 반드시 준다.
312
319
 
313
320
  Raises:
314
- ILOperationException: 서버가 거절했을 때.
321
+ ILOperationException: 서버가 거절했을 때. 그룹에 멤버가 있으면
322
+ ``MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)``, 다중 파티션 토픽에서
323
+ ``partition`` 을 생략하면 ``MIMQE_INVALID_ARGUMENT (Partition : REQUIRED, Available : 0~N)``.
315
324
  ILException: 통신 실패.
325
+
326
+ Warning:
327
+ 엔진 7.0.1.3417 이상에서는 그 구독(그룹)에 **멤버가 하나도 없을 때만** 된다. ``partition`` 을 생략한 seek 는
328
+ 파티션이 하나인 토픽에서만 받는다. 그보다 옛 엔진은 생략하면 거절하지 않고 **0번 파티션 하나만** 되감는다.
316
329
  """
317
330
  self._seek("offset:%d" % offset, None if partition < 0 else partition)
318
331
 
@@ -323,11 +336,16 @@ class ILAdminTopicSubscription:
323
336
  epoch_ms (int): 기준 시각(**epoch 밀리초**).
324
337
 
325
338
  Returns:
326
- int: 서버가 정한 새 오프셋.
339
+ int: 서버가 정한 새 오프셋. 엔진 7.0.1.3417 이상은 옮긴 파티션 중 번호가 가장 작은 파티션의 결과이고
340
+ (옮긴 것이 없으면 ``-1``), 옛 엔진은 0번 파티션의 결과다.
327
341
 
328
342
  Raises:
329
- ILOperationException: 서버가 거절했을 때.
343
+ ILOperationException: 서버가 거절했을 때. 그룹에 멤버가 있으면
344
+ ``MIMQE_TOPIC_REBALANCED (partition N is not owned by this member)``.
330
345
  ILException: 통신 실패.
346
+
347
+ Warning:
348
+ 엔진 7.0.1.3417 이상에서는 그 구독(그룹)에 **멤버가 하나도 없을 때만** 된다.
331
349
  """
332
350
  return self._seek("time:%d" % epoch_ms, None)
333
351
 
@@ -260,6 +260,13 @@ class ILC:
260
260
  #: 발행 v2 ACK. corrId + 봉투 code / 사유 + 배치 수 + 배치마다 {partition, code, baseOffset, timestamp,
261
261
  #: expectedSeq, 사유}. 봉투 헤더를 못 읽으면 봉투 code / 사유만 싣고 배치 수 0 이다.
262
262
  MIMQ_TOPIC_PUBLISH_V2_ACK_MESSAGE = 2041
263
+ #: 0.7.3 소비 프로토콜 v5(엔진 CONSV5-1): 요청 태그. 본문 ``[u16 1][i64 corrId]`` - 요청 바로 앞에 보내면 응답 바로 앞에
264
+ #: 같은 태그가 온다(응답 순서는 요청 순서와 다를 수 있다). 큐 PUT/GET, 트랜잭션, 발행에 달면 엔진이 연결을 끊는다.
265
+ MIMQ_REQ_TAG_MESSAGE = 2044
266
+ #: 0.7.3 다중 토픽 read 요청 / 응답(이진 BE). 응답 절 = 토픽, 구독, 상태(0 데이터 / 2 재배정 / 3 오류), 사유, 2042 본문.
267
+ #: 모든 항목이 비어 시간이 다하면 2046 대신 REPORT(``MIMQ_NO_NEW_MESSAGE``)가 온다.
268
+ MIMQ_TOPIC_FETCH_MULTI_MESSAGE = 2045
269
+ MIMQ_TOPIC_FETCH_MULTI_RESP_MESSAGE = 2046
263
270
  MIMQ_LONG_REQUEST_CODE_LENGTH = 6
264
271
  MIMQ_LONG_REQUEST_ARG_LENGTH = 70
265
272
  MIMQ_LIST_ITEM_COUNT_LENGTH = 5
@@ -454,6 +461,11 @@ class ILC:
454
461
  #: arg2=epoch. 실패 4157 이면 arg2=사유(없는 토픽은 MIMQE_OBJECT_NOT_FOUND, flush 창이면
455
462
  #: MIMQE_TOPIC_FLUSHED - 재시도할 수 있다). 데이터 연결과 관리 연결 릴레이 둘 다 받는다.
456
463
  MIMQ_TOPIC_METADATA = 3397
464
+ #: 0.7.4 CONSGRP-1(엔진 r3417): 이 세션을 (토픽, 구독) 그룹에서 뺀다. arg1=토픽, arg2=구독, arg3="". 곧바로
465
+ #: 재배정하고 커서는 남긴다. 성공 4156 이면 arg1 "1"(뺐다) / "0"(멤버가 아니었다). 구독이 없으면 4157
466
+ #: SUBSCRIPTION_NOT_FOUND, 그 (토픽, 구독)에 read 가 걸려 있으면(태그 롱폴 / FETCH_MULTI 항목) 4157
467
+ #: MIMQE_READ_IN_PROGRESS. 다시 read 하면 다시 든다(재배정 통지부터). r3416 이하 엔진은 모르는 요청으로 거절한다.
468
+ MIMQ_TOPIC_LEAVE = 3401
457
469
 
458
470
  # 토픽 응답 코드
459
471
  MIMQ_SUCCESSFUL_TOPIC_OPERATION = 4156