jetstream-api 0.4.4__tar.gz → 0.5.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 (25) hide show
  1. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/PKG-INFO +122 -6
  2. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/README.md +121 -5
  3. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/__init__.py +1 -1
  4. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/admin.py +6583 -6246
  5. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/common.py +11 -1
  6. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/msg.py +3 -0
  7. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/properties.py +4772 -4203
  8. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/qmgr.py +81 -51
  9. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/jetstream_api.egg-info/PKG-INFO +122 -6
  10. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/pyproject.toml +1 -1
  11. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/LICENSE.txt +0 -0
  12. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/admintopic.py +0 -0
  13. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/connectivity.py +0 -0
  14. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/exception.py +0 -0
  15. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/pattern.py +0 -0
  16. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/producer.py +0 -0
  17. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/py.typed +0 -0
  18. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/topic.py +0 -0
  19. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/ilink/util.py +0 -0
  20. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/jetstream_api.egg-info/SOURCES.txt +0 -0
  21. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/jetstream_api.egg-info/dependency_links.txt +0 -0
  22. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/jetstream_api.egg-info/requires.txt +0 -0
  23. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/jetstream_api.egg-info/top_level.txt +0 -0
  24. {jetstream_api-0.4.4 → jetstream_api-0.5.0}/setup.cfg +0 -0
  25. {jetstream_api-0.4.4 → jetstream_api-0.5.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.4.4
3
+ Version: 0.5.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
@@ -146,13 +146,20 @@ qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
146
146
  ```
147
147
 
148
148
  접속에 성공하면 핸드셰이크 직후 나머지 노드 주소를 서버에서 받아 캐싱하므로
149
- **주소는 하나만 주면 됩니다.** 목록은 첫 접속 시점의 장애까지 대비할 때 씁니다.
149
+ **주소는 하나만 주면 됩니다.** 팔로워를 지목해도 거부에 실린 리더 힌트를 따라 자동으로
150
+ 리더에 붙으며, 이는 **단일 주소든 목록이든 마찬가지**입니다. 그때 알게 된 리더 주소는
151
+ **후보 캐시에 들어가** 목록으로 받은 주소와 똑같이 쓰이고, `set_endpoint_cache_file()` 로
152
+ 경로를 지정해 뒀다면 파일에도 반영됩니다(이미 있으면 그대로 둡니다).
153
+
154
+ 목록은 **첫 접속 시점**의 장애까지 대비할 때 씁니다 — 하나만 준 그 주소가 죽어 있으면
155
+ 힌트를 줄 상대조차 없기 때문입니다.
150
156
 
151
157
  ### 0.4.4 변경 - 자동 재접속은 옵션이 아니라 기본 동작
152
158
 
153
159
  캐싱된 endpoint 목록이 있으면(= 클러스터) 통신 장애로 실패한 put/get 을 페일오버
154
- 재접속 후 **한 번 자동 재시도합니다.** `set_auto_reconnect()` 는 더 이상 필요 없으며,
155
- 불러도 무시됩니다(기존 코드 호환용으로만 남겨 두었습니다).
160
+ 재접속 후 **한 번 자동 재시도합니다.** 켜고 끄는 설정은 없습니다
161
+ (`set_auto_reconnect()` 는 제거했습니다 — 아무 일도 하지 않는 세터를 남겨 두면
162
+ "껐는데 왜 재접속하냐"는 혼란만 생깁니다).
156
163
 
157
164
  | 상황 | 동작 |
158
165
  |---|---|
@@ -161,8 +168,117 @@ qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
161
168
  | endpoint 목록 없음 (비클러스터/구엔진) | 예외를 올립니다 - 갈 곳이 없습니다 |
162
169
 
163
170
  > 재시도는 **at-least-once** 입니다. 서버가 처리한 뒤 응답이 유실된 시점에 재시도하면
164
- > **중복 put 이 생길 수 있습니다.** 멱등하지 않은 처리라면 메시지 ID 로 중복을 걸러야
165
- > 합니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
171
+ > **중복 put 이 생길 수 있습니다.** 재시도는 같은 msgId 로 나가므로 수신측에서 메시지 ID
172
+ > 로 걸러낼 수 있습니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
173
+
174
+ ### 장애 전환(failover) 시 앱이 해야 할 일
175
+
176
+ 리더가 죽으면 새 리더가 뽑힐 때까지 **아무도 접속을 받지 않습니다.** 실측(2노드 클러스터,
177
+ 엔진 7.0.1.3341) **약 15~20초**가 걸립니다. 그 사이의 재접속 시도는 정상적으로 실패하므로,
178
+ **한 번 실패했다고 끝내지 말고 재시도**해야 합니다.
179
+
180
+ 세션 종류에 따라 앱이 할 일이 갈립니다.
181
+
182
+ | 세션 | 실패 시점에 미커밋 | 앱이 할 일 |
183
+ |---|---|---|
184
+ | auto-commit | 없음 | **아무것도 안 해도 됩니다.** 그 연산을 다시 부르기만 하면 라이브러리가 재접속·재시도합니다 |
185
+ | transacted | 없음 | 위와 같습니다 |
186
+ | transacted | 있음 | `reconnect()` 로 자리를 옮기고 **트랜잭션을 처음부터 다시** 수행해야 합니다 |
187
+
188
+ `access_queue()` 는 **자동 재접속 대상이 아닙니다.** 끊긴 세션에서 부르면 그대로 실패하니,
189
+ 재접속에 성공한 **뒤에** 핸들을 다시 얻으십시오. 기존 핸들을 계속 쓰는 쪽은 스스로 복구됩니다.
190
+
191
+ #### (1) 트랜잭션 재수행이 필요 없는 경우 — auto-commit
192
+
193
+ ```python
194
+ import time
195
+ from ilink.qmgr import ILQmgr
196
+ from ilink.exception import ILException, ILSessionException, ILOperationException
197
+
198
+ q = ILQmgr()
199
+ q.set_endpoint_cache_file("/var/run/myapp/ilink-endpoints.txt") # 재기동 대비(선택)
200
+ q.connect("10.0.0.1", 5000, "collector", True) # auto-commit
201
+ queue = q.access_queue("APP.EVENTS") # 핸들은 한 번만 얻는다
202
+
203
+ def publish(payload):
204
+ """페일오버가 나도 이 함수는 그대로 둔다 - 라이브러리가 복구한다."""
205
+ for attempt in range(1, 11):
206
+ try:
207
+ return queue.put(payload) # 실패하면 내부에서 재접속 + 1회 재시도
208
+ except ILOperationException:
209
+ raise # 서버가 거절 - 재시도해도 같다
210
+ except (ILException, ILSessionException):
211
+ if attempt == 10:
212
+ raise # 승격이 20초 넘게 안 끝났다
213
+ time.sleep(3) # 리더 승격을 기다렸다가 다시
214
+ ```
215
+
216
+ 앱 코드에 `reconnect()` 가 없다는 점이 요지입니다. 같은 큐 핸들로 `put` 을 다시 부르기만
217
+ 하면, 승격이 끝난 시점의 호출이 새 리더에 붙어 성공합니다(실측 20.6초에 복구).
218
+
219
+ #### (2) 트랜잭션 재수행이 필요한 경우 — transacted
220
+
221
+ ```python
222
+ import time
223
+ from ilink.qmgr import ILQmgr
224
+ from ilink.exception import ILException, ILSessionException, ILOperationException
225
+
226
+ MAX_RETRY = 10
227
+
228
+ def process_batch(records):
229
+ """한 번의 트랜잭션 = 전부 커밋되거나 전부 없던 일이 된다."""
230
+ q = ILQmgr()
231
+ q.set_endpoint_cache_file("/var/run/myapp/ilink-endpoints.txt")
232
+ q.connect("10.0.0.1", 5000, "billing-svc", False) # transacted
233
+ try:
234
+ for attempt in range(1, MAX_RETRY + 1):
235
+ try:
236
+ queue = q.access_queue("APP.ORDERS") # 재접속 뒤 핸들을 다시 얻는다
237
+ for rec in records:
238
+ queue.put(rec)
239
+ q.commit() # 여기까지 와야 확정된다
240
+ return
241
+ except ILOperationException:
242
+ q.rollback() # 서버 거절 - 재시도 무의미
243
+ raise
244
+ except (ILException, ILSessionException):
245
+ if attempt == MAX_RETRY:
246
+ raise
247
+ try:
248
+ q.reconnect() # 승격 전이면 여기서 또 실패한다
249
+ except Exception:
250
+ time.sleep(3) # 기다렸다가 다음 회차에 다시
251
+ # 루프 처음으로 -> 배치 전체를 다시 넣는다
252
+ finally:
253
+ q.disconnect()
254
+ ```
255
+
256
+ 읽어야 할 포인트 셋입니다.
257
+
258
+ 1. **재시도 단위는 트랜잭션 전체**입니다. 실패 지점부터 이어붙이면 안 됩니다 — 앞서 넣은
259
+ 것들도 커밋되지 않았으므로 함께 사라집니다(실측: 미커밋 1건 유실, 재수행 후 depth 2 정상).
260
+ 2. **`reconnect()` 자체가 실패할 수 있습니다.** 승격 전에 부르면 실패하는 게 정상이라,
261
+ 루프 안에서 간격을 두고 다시 불러야 합니다(실측 2번째 시도, 16.5초에 성공).
262
+ 3. **`ILOperationException` 과 통신 예외를 갈라 잡습니다.** 전자는 서버가 판단해 거절한
263
+ 것이라 재시도가 무의미하고, 후자만 재접속 대상입니다. 파이썬의 예외 클래스는 평면
264
+ 구조라 `ILSessionException` 은 `ILException` 의 하위 타입이 **아닙니다** — 통신 장애를
265
+ 잡으려면 **둘 다** 적어야 합니다(자바는 모두 `ILException` 으로 감싸므로 하나면 됩니다).
266
+
267
+ #### 앱이 재기동되는 경우
268
+
269
+ 캐싱은 메모리에만 있으므로 프로세스가 죽으면 사라집니다. 설정에 남은 옛 리더 주소로 다시
270
+ 붙을 때 이렇게 갈립니다.
271
+
272
+ | 옛 리더의 상태 | 결과 |
273
+ |---|---|
274
+ | 살아 있고 **팔로워로 강등** | 그 노드가 리더 힌트를 주므로 **자동으로 새 리더에 접속** |
275
+ | **죽어 있음** | 갈 곳이 없어 실패 — `set_endpoint_cache_file()` 을 쓰거나 주소를 목록으로 주십시오 |
276
+
277
+ ```python
278
+ q = ILQmgr()
279
+ q.set_endpoint_cache_file("/var/run/myapp/ilink-endpoints.txt")
280
+ q.connect("10.0.0.1", 5000, "APP", True) # 이 주소가 죽어 있어도 파일 후보로 붙는다
281
+ ```
166
282
 
167
283
  ## 0.4.0 신규 - 관리 연결 하나로 pub/sub
168
284
 
@@ -100,13 +100,20 @@ qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
100
100
  ```
101
101
 
102
102
  접속에 성공하면 핸드셰이크 직후 나머지 노드 주소를 서버에서 받아 캐싱하므로
103
- **주소는 하나만 주면 됩니다.** 목록은 첫 접속 시점의 장애까지 대비할 때 씁니다.
103
+ **주소는 하나만 주면 됩니다.** 팔로워를 지목해도 거부에 실린 리더 힌트를 따라 자동으로
104
+ 리더에 붙으며, 이는 **단일 주소든 목록이든 마찬가지**입니다. 그때 알게 된 리더 주소는
105
+ **후보 캐시에 들어가** 목록으로 받은 주소와 똑같이 쓰이고, `set_endpoint_cache_file()` 로
106
+ 경로를 지정해 뒀다면 파일에도 반영됩니다(이미 있으면 그대로 둡니다).
107
+
108
+ 목록은 **첫 접속 시점**의 장애까지 대비할 때 씁니다 — 하나만 준 그 주소가 죽어 있으면
109
+ 힌트를 줄 상대조차 없기 때문입니다.
104
110
 
105
111
  ### 0.4.4 변경 - 자동 재접속은 옵션이 아니라 기본 동작
106
112
 
107
113
  캐싱된 endpoint 목록이 있으면(= 클러스터) 통신 장애로 실패한 put/get 을 페일오버
108
- 재접속 후 **한 번 자동 재시도합니다.** `set_auto_reconnect()` 는 더 이상 필요 없으며,
109
- 불러도 무시됩니다(기존 코드 호환용으로만 남겨 두었습니다).
114
+ 재접속 후 **한 번 자동 재시도합니다.** 켜고 끄는 설정은 없습니다
115
+ (`set_auto_reconnect()` 는 제거했습니다 — 아무 일도 하지 않는 세터를 남겨 두면
116
+ "껐는데 왜 재접속하냐"는 혼란만 생깁니다).
110
117
 
111
118
  | 상황 | 동작 |
112
119
  |---|---|
@@ -115,8 +122,117 @@ qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
115
122
  | endpoint 목록 없음 (비클러스터/구엔진) | 예외를 올립니다 - 갈 곳이 없습니다 |
116
123
 
117
124
  > 재시도는 **at-least-once** 입니다. 서버가 처리한 뒤 응답이 유실된 시점에 재시도하면
118
- > **중복 put 이 생길 수 있습니다.** 멱등하지 않은 처리라면 메시지 ID 로 중복을 걸러야
119
- > 합니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
125
+ > **중복 put 이 생길 수 있습니다.** 재시도는 같은 msgId 로 나가므로 수신측에서 메시지 ID
126
+ > 로 걸러낼 수 있습니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
127
+
128
+ ### 장애 전환(failover) 시 앱이 해야 할 일
129
+
130
+ 리더가 죽으면 새 리더가 뽑힐 때까지 **아무도 접속을 받지 않습니다.** 실측(2노드 클러스터,
131
+ 엔진 7.0.1.3341) **약 15~20초**가 걸립니다. 그 사이의 재접속 시도는 정상적으로 실패하므로,
132
+ **한 번 실패했다고 끝내지 말고 재시도**해야 합니다.
133
+
134
+ 세션 종류에 따라 앱이 할 일이 갈립니다.
135
+
136
+ | 세션 | 실패 시점에 미커밋 | 앱이 할 일 |
137
+ |---|---|---|
138
+ | auto-commit | 없음 | **아무것도 안 해도 됩니다.** 그 연산을 다시 부르기만 하면 라이브러리가 재접속·재시도합니다 |
139
+ | transacted | 없음 | 위와 같습니다 |
140
+ | transacted | 있음 | `reconnect()` 로 자리를 옮기고 **트랜잭션을 처음부터 다시** 수행해야 합니다 |
141
+
142
+ `access_queue()` 는 **자동 재접속 대상이 아닙니다.** 끊긴 세션에서 부르면 그대로 실패하니,
143
+ 재접속에 성공한 **뒤에** 핸들을 다시 얻으십시오. 기존 핸들을 계속 쓰는 쪽은 스스로 복구됩니다.
144
+
145
+ #### (1) 트랜잭션 재수행이 필요 없는 경우 — auto-commit
146
+
147
+ ```python
148
+ import time
149
+ from ilink.qmgr import ILQmgr
150
+ from ilink.exception import ILException, ILSessionException, ILOperationException
151
+
152
+ q = ILQmgr()
153
+ q.set_endpoint_cache_file("/var/run/myapp/ilink-endpoints.txt") # 재기동 대비(선택)
154
+ q.connect("10.0.0.1", 5000, "collector", True) # auto-commit
155
+ queue = q.access_queue("APP.EVENTS") # 핸들은 한 번만 얻는다
156
+
157
+ def publish(payload):
158
+ """페일오버가 나도 이 함수는 그대로 둔다 - 라이브러리가 복구한다."""
159
+ for attempt in range(1, 11):
160
+ try:
161
+ return queue.put(payload) # 실패하면 내부에서 재접속 + 1회 재시도
162
+ except ILOperationException:
163
+ raise # 서버가 거절 - 재시도해도 같다
164
+ except (ILException, ILSessionException):
165
+ if attempt == 10:
166
+ raise # 승격이 20초 넘게 안 끝났다
167
+ time.sleep(3) # 리더 승격을 기다렸다가 다시
168
+ ```
169
+
170
+ 앱 코드에 `reconnect()` 가 없다는 점이 요지입니다. 같은 큐 핸들로 `put` 을 다시 부르기만
171
+ 하면, 승격이 끝난 시점의 호출이 새 리더에 붙어 성공합니다(실측 20.6초에 복구).
172
+
173
+ #### (2) 트랜잭션 재수행이 필요한 경우 — transacted
174
+
175
+ ```python
176
+ import time
177
+ from ilink.qmgr import ILQmgr
178
+ from ilink.exception import ILException, ILSessionException, ILOperationException
179
+
180
+ MAX_RETRY = 10
181
+
182
+ def process_batch(records):
183
+ """한 번의 트랜잭션 = 전부 커밋되거나 전부 없던 일이 된다."""
184
+ q = ILQmgr()
185
+ q.set_endpoint_cache_file("/var/run/myapp/ilink-endpoints.txt")
186
+ q.connect("10.0.0.1", 5000, "billing-svc", False) # transacted
187
+ try:
188
+ for attempt in range(1, MAX_RETRY + 1):
189
+ try:
190
+ queue = q.access_queue("APP.ORDERS") # 재접속 뒤 핸들을 다시 얻는다
191
+ for rec in records:
192
+ queue.put(rec)
193
+ q.commit() # 여기까지 와야 확정된다
194
+ return
195
+ except ILOperationException:
196
+ q.rollback() # 서버 거절 - 재시도 무의미
197
+ raise
198
+ except (ILException, ILSessionException):
199
+ if attempt == MAX_RETRY:
200
+ raise
201
+ try:
202
+ q.reconnect() # 승격 전이면 여기서 또 실패한다
203
+ except Exception:
204
+ time.sleep(3) # 기다렸다가 다음 회차에 다시
205
+ # 루프 처음으로 -> 배치 전체를 다시 넣는다
206
+ finally:
207
+ q.disconnect()
208
+ ```
209
+
210
+ 읽어야 할 포인트 셋입니다.
211
+
212
+ 1. **재시도 단위는 트랜잭션 전체**입니다. 실패 지점부터 이어붙이면 안 됩니다 — 앞서 넣은
213
+ 것들도 커밋되지 않았으므로 함께 사라집니다(실측: 미커밋 1건 유실, 재수행 후 depth 2 정상).
214
+ 2. **`reconnect()` 자체가 실패할 수 있습니다.** 승격 전에 부르면 실패하는 게 정상이라,
215
+ 루프 안에서 간격을 두고 다시 불러야 합니다(실측 2번째 시도, 16.5초에 성공).
216
+ 3. **`ILOperationException` 과 통신 예외를 갈라 잡습니다.** 전자는 서버가 판단해 거절한
217
+ 것이라 재시도가 무의미하고, 후자만 재접속 대상입니다. 파이썬의 예외 클래스는 평면
218
+ 구조라 `ILSessionException` 은 `ILException` 의 하위 타입이 **아닙니다** — 통신 장애를
219
+ 잡으려면 **둘 다** 적어야 합니다(자바는 모두 `ILException` 으로 감싸므로 하나면 됩니다).
220
+
221
+ #### 앱이 재기동되는 경우
222
+
223
+ 캐싱은 메모리에만 있으므로 프로세스가 죽으면 사라집니다. 설정에 남은 옛 리더 주소로 다시
224
+ 붙을 때 이렇게 갈립니다.
225
+
226
+ | 옛 리더의 상태 | 결과 |
227
+ |---|---|
228
+ | 살아 있고 **팔로워로 강등** | 그 노드가 리더 힌트를 주므로 **자동으로 새 리더에 접속** |
229
+ | **죽어 있음** | 갈 곳이 없어 실패 — `set_endpoint_cache_file()` 을 쓰거나 주소를 목록으로 주십시오 |
230
+
231
+ ```python
232
+ q = ILQmgr()
233
+ q.set_endpoint_cache_file("/var/run/myapp/ilink-endpoints.txt")
234
+ q.connect("10.0.0.1", 5000, "APP", True) # 이 주소가 죽어 있어도 파일 후보로 붙는다
235
+ ```
120
236
 
121
237
  ## 0.4.0 신규 - 관리 연결 하나로 pub/sub
122
238
 
@@ -1,6 +1,6 @@
1
1
  """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
2
 
3
- __version__ = "0.4.4"
3
+ __version__ = "0.5.0"
4
4
 
5
5
  # --- Admin / Service ---
6
6
  from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser