jetstream-api 0.8.0__tar.gz → 0.9.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.
- {jetstream_api-0.8.0/jetstream_api.egg-info → jetstream_api-0.9.0}/PKG-INFO +28 -7
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/README.md +25 -6
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/__init__.py +1 -1
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/_codec.py +173 -65
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/msg.py +2 -2
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/producer.py +35 -34
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/topic.py +1 -1
- {jetstream_api-0.8.0 → jetstream_api-0.9.0/jetstream_api.egg-info}/PKG-INFO +28 -7
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/jetstream_api.egg-info/requires.txt +3 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/pyproject.toml +3 -1
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/LICENSE.txt +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/admin.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/admintopic.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/common.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/connectivity.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/exception.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/partitioner.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/pattern.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/properties.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/py.typed +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/qmgr.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/ilink/util.py +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/jetstream_api.egg-info/SOURCES.txt +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/jetstream_api.egg-info/dependency_links.txt +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/jetstream_api.egg-info/top_level.txt +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.0}/setup.cfg +0 -0
- {jetstream_api-0.8.0 → jetstream_api-0.9.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.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -40,6 +40,8 @@ Classifier: Topic :: System :: Networking
|
|
|
40
40
|
Requires-Python: >=3.9
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
License-File: LICENSE.txt
|
|
43
|
+
Provides-Extra: lz4
|
|
44
|
+
Requires-Dist: lz4>=4.0; extra == "lz4"
|
|
43
45
|
Provides-Extra: zstd
|
|
44
46
|
Requires-Dist: zstandard>=0.22; extra == "zstd"
|
|
45
47
|
Provides-Extra: test
|
|
@@ -52,11 +54,35 @@ Python으로 작성된 ILink 클라이언트 API입니다.
|
|
|
52
54
|
|
|
53
55
|
## 시스템 요구 사항
|
|
54
56
|
시스템에 Python 3.9 이상, pip가 설치되어 있어야 합니다. 필수 의존성은 없습니다(표준 라이브러리만 씁니다).
|
|
55
|
-
|
|
57
|
+
lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치 압축을 쓰려면 `pip install "jetstream-api[zstd]"` 로
|
|
58
|
+
설치합니다(zstd 는 Python 3.14 이상이면 표준 라이브러리로 됩니다). 둘 다 쓰려면 `pip install "jetstream-api[lz4,zstd]"` 입니다.
|
|
56
59
|
|
|
57
60
|
## 설치 방법
|
|
58
61
|
pip install jetstream-api
|
|
59
62
|
|
|
63
|
+
## 0.9.0 변경 - 압축 코덱 lz4 / zstd, gzip 제거 (자바 v2.5.0 과 같음)
|
|
64
|
+
|
|
65
|
+
- **코덱은 `none` / `lz4` / `zstd` 입니다**: gzip 을 빼고 lz4 를 넣었습니다(사용자 결정). 클라이언트 CPU·지연이 중요하면
|
|
66
|
+
`lz4`, 디스크·용량이 중요하면 `zstd` 를 고르세요. 기본값은 여전히 압축하지 않음(`none`)입니다.
|
|
67
|
+
- **공개 API 제거**: `compressionGzipLevel()` / `compression_gzip_level()` / `get_compression_gzip_level()` /
|
|
68
|
+
`getCompressionGzipLevel()`. `compressionType("gzip")` 은 `ValueError`
|
|
69
|
+
(`compressionType must be none / lz4 / zstd (got 'gzip' - gzip and snappy are not supported)`)입니다.
|
|
70
|
+
- **추가**: `compressionType("lz4")`, `compressionLz4Level(n)` (`compression_lz4_level`) - `1`~`17`, 기본 `9`, 게터
|
|
71
|
+
`get_compression_lz4_level()` / `getCompressionLz4Level()`. 뜻은 Kafka `compression.lz4.level` 과 같습니다 - `9`(기본)는
|
|
72
|
+
빠른 압축기이고, `9` 가 아닌 값은 그 레벨의 LZ4HC(훨씬 느리고 조금 더 줄입니다)입니다. 레벨 12 이상은 결과가 같습니다.
|
|
73
|
+
파이썬 lz4 모듈은 레벨 1~2 를 빠른 압축기와 같게 다룹니다(자바 lz4-java 는 1~2 도 HC 계열 - 라이브러리 차이).
|
|
74
|
+
- **lz4 는 선택 설치입니다**: `pip install "jetstream-api[lz4]"`(`lz4>=4.0`). 모듈이 없으면 `compressionType("lz4")` producer
|
|
75
|
+
생성이 연결하기 전에 `ILOperationException`(`MIMQE_NOT_SUPPORTED (compressionType lz4: no lz4 module - pip install
|
|
76
|
+
"jetstream-api[lz4]")`)으로 멈추고, lz4 배치를 받은 구독 read 는 원인 `MIMQE_NOT_SUPPORTED (codec 3 lz4: no lz4 module - ...)`
|
|
77
|
+
로 그 배치에서 멈춥니다(zstd 모듈 없음과 같은 `MIMQC_USER_FAULT` / `MIMQE_NOT_SUPPORTED`). 발행하는 코덱의 모듈을 구독하는
|
|
78
|
+
쪽에도 설치하세요.
|
|
79
|
+
- **lz4 와이어**: 배치 attributes 코덱 3, 표준 LZ4 frame(`lz4` 명령과 같은 틀)입니다. producer 는 64KB **독립 블록** + 원문
|
|
80
|
+
크기로 만들고 체크섬은 넣지 않습니다(배치 CRC 가 압축 바이트를 덮습니다) - 자바 v2.5.0 도 같은 틀로 만듭니다. 소비는
|
|
81
|
+
이어 붙인 frame 을 모두 풀고, 잘림 / frame 뒤에 남은 바이트 / 푼 크기 128MiB 초과 / 깨진 frame 은
|
|
82
|
+
`MIMQE_TOPIC_CODEC_ERROR (lz4: ...)` 로 그 배치에서 멈춥니다(0.8.0 의 "풀지 못한 배치" 규칙 그대로).
|
|
83
|
+
- 압축은 예전처럼 producer 의 I/O 스레드가 하고, 줄지 않는 배치는 압축하지 않고 보냅니다. lz4 모듈도 압축하는 동안 GIL 을
|
|
84
|
+
놓습니다.
|
|
85
|
+
|
|
60
86
|
## 0.8.0 변경 - 배치 압축 gzip / zstd (자바 v2.4.0 과 같음)
|
|
61
87
|
|
|
62
88
|
- **엔진 요구**: 압축 배치는 **엔진 7.0.1.3419 이상**(CMPR-1)에서만 받습니다. 그보다 옛 엔진은 압축 배치를 알아보지 못해
|
|
@@ -109,11 +135,6 @@ pip install jetstream-api
|
|
|
109
135
|
떼어 내지 않고 그 파티션만 되감으며, 같은 응답의 다른 토픽 레코드는 그대로 줍니다.
|
|
110
136
|
- 패턴 구독 `read_batch` 는 요청 실패(요청 전체 거절 / 통신 실패)에서도 이 호출에서 이미 모은 레코드를 돌려주고 오류는 다음
|
|
111
137
|
호출이 올립니다(예전 판은 모은 레코드를 버렸는데 AUTO 커밋 좌표는 이미 병합돼 있어 앱이 못 본 레코드가 커밋됐습니다).
|
|
112
|
-
- **옛 클라이언트**: 엔진은 배치 read 에 저장한 바이트를 그대로 보내므로, 압축 토픽을 소비하는 구독자는 **0.8.0 / 자바 v2.4.0
|
|
113
|
-
이상**이어야 합니다. 압축을 켜기 전에 그 토픽의 소비자를 먼저 올리세요. 옛 판 중 압축 토픽을 읽는 것은 엔진 단건 read 를
|
|
114
|
-
쓰는 판뿐입니다 - 파이썬 0.6.x 의 `read()`, 자바 v2.2.x 의 prefetch 를 끈 `read()`(엔진이 풀어서 줍니다). 파이썬 0.7.x 와
|
|
115
|
-
자바 v2.3.x 는 `read()` 도 배치 read 라서 압축 배치에서 형식 오류(`MIMQC_FATAL_ERROR (MIMQE_INTERNAL_ERROR)`, 원인
|
|
116
|
-
`truncated topic batch ...`)로 멈춥니다 - 조용히 틀린 레코드를 주지는 않습니다(pv2test 엔진 7.0.1.3419 실측).
|
|
117
138
|
- **NIO 커넥터 송신은 기한 하나를 씁니다**(`TCPConnectorNIO`, `set_conn` 으로 고른 경우만): 송신 한 번 전체가 접속 대기 시간
|
|
118
139
|
(최소 1 s)을 기한으로 씁니다. 기한이 지나면 `MIMQE_SESSION_TIMEOUT`(`write waiting time has exceeded the time limit : [Nms]
|
|
119
140
|
/ written : [n / 전체]`)을 내고 연결을 닫습니다. 전에는 보낼 자리가 날 때까지 기다릴 때마다 한도를 새로 주어, 상대가
|
|
@@ -4,11 +4,35 @@ Python으로 작성된 ILink 클라이언트 API입니다.
|
|
|
4
4
|
|
|
5
5
|
## 시스템 요구 사항
|
|
6
6
|
시스템에 Python 3.9 이상, pip가 설치되어 있어야 합니다. 필수 의존성은 없습니다(표준 라이브러리만 씁니다).
|
|
7
|
-
|
|
7
|
+
lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치 압축을 쓰려면 `pip install "jetstream-api[zstd]"` 로
|
|
8
|
+
설치합니다(zstd 는 Python 3.14 이상이면 표준 라이브러리로 됩니다). 둘 다 쓰려면 `pip install "jetstream-api[lz4,zstd]"` 입니다.
|
|
8
9
|
|
|
9
10
|
## 설치 방법
|
|
10
11
|
pip install jetstream-api
|
|
11
12
|
|
|
13
|
+
## 0.9.0 변경 - 압축 코덱 lz4 / zstd, gzip 제거 (자바 v2.5.0 과 같음)
|
|
14
|
+
|
|
15
|
+
- **코덱은 `none` / `lz4` / `zstd` 입니다**: gzip 을 빼고 lz4 를 넣었습니다(사용자 결정). 클라이언트 CPU·지연이 중요하면
|
|
16
|
+
`lz4`, 디스크·용량이 중요하면 `zstd` 를 고르세요. 기본값은 여전히 압축하지 않음(`none`)입니다.
|
|
17
|
+
- **공개 API 제거**: `compressionGzipLevel()` / `compression_gzip_level()` / `get_compression_gzip_level()` /
|
|
18
|
+
`getCompressionGzipLevel()`. `compressionType("gzip")` 은 `ValueError`
|
|
19
|
+
(`compressionType must be none / lz4 / zstd (got 'gzip' - gzip and snappy are not supported)`)입니다.
|
|
20
|
+
- **추가**: `compressionType("lz4")`, `compressionLz4Level(n)` (`compression_lz4_level`) - `1`~`17`, 기본 `9`, 게터
|
|
21
|
+
`get_compression_lz4_level()` / `getCompressionLz4Level()`. 뜻은 Kafka `compression.lz4.level` 과 같습니다 - `9`(기본)는
|
|
22
|
+
빠른 압축기이고, `9` 가 아닌 값은 그 레벨의 LZ4HC(훨씬 느리고 조금 더 줄입니다)입니다. 레벨 12 이상은 결과가 같습니다.
|
|
23
|
+
파이썬 lz4 모듈은 레벨 1~2 를 빠른 압축기와 같게 다룹니다(자바 lz4-java 는 1~2 도 HC 계열 - 라이브러리 차이).
|
|
24
|
+
- **lz4 는 선택 설치입니다**: `pip install "jetstream-api[lz4]"`(`lz4>=4.0`). 모듈이 없으면 `compressionType("lz4")` producer
|
|
25
|
+
생성이 연결하기 전에 `ILOperationException`(`MIMQE_NOT_SUPPORTED (compressionType lz4: no lz4 module - pip install
|
|
26
|
+
"jetstream-api[lz4]")`)으로 멈추고, lz4 배치를 받은 구독 read 는 원인 `MIMQE_NOT_SUPPORTED (codec 3 lz4: no lz4 module - ...)`
|
|
27
|
+
로 그 배치에서 멈춥니다(zstd 모듈 없음과 같은 `MIMQC_USER_FAULT` / `MIMQE_NOT_SUPPORTED`). 발행하는 코덱의 모듈을 구독하는
|
|
28
|
+
쪽에도 설치하세요.
|
|
29
|
+
- **lz4 와이어**: 배치 attributes 코덱 3, 표준 LZ4 frame(`lz4` 명령과 같은 틀)입니다. producer 는 64KB **독립 블록** + 원문
|
|
30
|
+
크기로 만들고 체크섬은 넣지 않습니다(배치 CRC 가 압축 바이트를 덮습니다) - 자바 v2.5.0 도 같은 틀로 만듭니다. 소비는
|
|
31
|
+
이어 붙인 frame 을 모두 풀고, 잘림 / frame 뒤에 남은 바이트 / 푼 크기 128MiB 초과 / 깨진 frame 은
|
|
32
|
+
`MIMQE_TOPIC_CODEC_ERROR (lz4: ...)` 로 그 배치에서 멈춥니다(0.8.0 의 "풀지 못한 배치" 규칙 그대로).
|
|
33
|
+
- 압축은 예전처럼 producer 의 I/O 스레드가 하고, 줄지 않는 배치는 압축하지 않고 보냅니다. lz4 모듈도 압축하는 동안 GIL 을
|
|
34
|
+
놓습니다.
|
|
35
|
+
|
|
12
36
|
## 0.8.0 변경 - 배치 압축 gzip / zstd (자바 v2.4.0 과 같음)
|
|
13
37
|
|
|
14
38
|
- **엔진 요구**: 압축 배치는 **엔진 7.0.1.3419 이상**(CMPR-1)에서만 받습니다. 그보다 옛 엔진은 압축 배치를 알아보지 못해
|
|
@@ -61,11 +85,6 @@ pip install jetstream-api
|
|
|
61
85
|
떼어 내지 않고 그 파티션만 되감으며, 같은 응답의 다른 토픽 레코드는 그대로 줍니다.
|
|
62
86
|
- 패턴 구독 `read_batch` 는 요청 실패(요청 전체 거절 / 통신 실패)에서도 이 호출에서 이미 모은 레코드를 돌려주고 오류는 다음
|
|
63
87
|
호출이 올립니다(예전 판은 모은 레코드를 버렸는데 AUTO 커밋 좌표는 이미 병합돼 있어 앱이 못 본 레코드가 커밋됐습니다).
|
|
64
|
-
- **옛 클라이언트**: 엔진은 배치 read 에 저장한 바이트를 그대로 보내므로, 압축 토픽을 소비하는 구독자는 **0.8.0 / 자바 v2.4.0
|
|
65
|
-
이상**이어야 합니다. 압축을 켜기 전에 그 토픽의 소비자를 먼저 올리세요. 옛 판 중 압축 토픽을 읽는 것은 엔진 단건 read 를
|
|
66
|
-
쓰는 판뿐입니다 - 파이썬 0.6.x 의 `read()`, 자바 v2.2.x 의 prefetch 를 끈 `read()`(엔진이 풀어서 줍니다). 파이썬 0.7.x 와
|
|
67
|
-
자바 v2.3.x 는 `read()` 도 배치 read 라서 압축 배치에서 형식 오류(`MIMQC_FATAL_ERROR (MIMQE_INTERNAL_ERROR)`, 원인
|
|
68
|
-
`truncated topic batch ...`)로 멈춥니다 - 조용히 틀린 레코드를 주지는 않습니다(pv2test 엔진 7.0.1.3419 실측).
|
|
69
88
|
- **NIO 커넥터 송신은 기한 하나를 씁니다**(`TCPConnectorNIO`, `set_conn` 으로 고른 경우만): 송신 한 번 전체가 접속 대기 시간
|
|
70
89
|
(최소 1 s)을 기한으로 씁니다. 기한이 지나면 `MIMQE_SESSION_TIMEOUT`(`write waiting time has exceeded the time limit : [Nms]
|
|
71
90
|
/ written : [n / 전체]`)을 내고 연결을 닫습니다. 전에는 보낼 자리가 날 때까지 기다릴 때마다 한도를 새로 주어, 상대가
|
|
@@ -1,42 +1,44 @@
|
|
|
1
|
-
"""배치 압축 코덱(0.8.0). **내부 모듈이다** - 발행과 소비가 함께 쓴다.
|
|
1
|
+
"""배치 압축 코덱(0.8.0, 0.9.0 에서 gzip 을 빼고 lz4 를 넣었다). **내부 모듈이다** - 발행과 소비가 함께 쓴다.
|
|
2
2
|
|
|
3
3
|
발행 v2 파티션 배치(= 저장 v4 배치)의 레코드 열을 압축하고 푼다. 와이어(엔진 CMPR-1):
|
|
4
4
|
|
|
5
5
|
- 배치 헤더 52바이트는 압축하지 않는다. 레코드 열 ``[52, batchLength)`` 만 압축한다.
|
|
6
|
-
- 헤더 ``attributes`` 의 하위 3비트가 코덱이다: 0 없음 /
|
|
6
|
+
- 헤더 ``attributes`` 의 하위 3비트가 코덱이다: 0 없음 / 3 lz4 / 4 zstd. 나머지 비트는 0 이다.
|
|
7
7
|
- ``recordCount`` 는 원래 레코드 수, ``batchLength`` 는 압축한 뒤 길이(52 + 압축 길이)이고, 배치 CRC 는 압축한
|
|
8
8
|
바이트 ``[28, batchLength)`` 가 대상이다(압축 -> 헤더 -> CRC 순서).
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
- lz4 는 표준 LZ4 frame 이고 zstd 는 표준 프레임이다. 둘 다 이어 붙인 여러 frame 을 받고, 끝난 뒤 남는 바이트는
|
|
10
|
+
거절한다. 푼 크기 상한은 128MiB 다.
|
|
11
|
+
- lz4 frame 은 블록 독립이어야 한다 - 엔진이 블록 연결형 frame 을 발행에서 거절한다(``lz4: linked blocks not supported``).
|
|
12
|
+
producer 는 64KB 독립 블록으로 만들고 원문 크기를 frame 에 싣는다. 체크섬은 쓰지 않는다(배치 CRC 가 압축 바이트를 덮는다).
|
|
13
|
+
|
|
14
|
+
lz4 / zstd 모듈은 필수 의존성이 아니다. lz4 는 ``lz4`` 패키지(``pip install "jetstream-api[lz4]"``), zstd 는 표준
|
|
15
|
+
라이브러리 ``compression.zstd``(파이썬 3.14+)를 먼저, 없으면 ``zstandard`` 패키지를 찾는다. 모듈이 없으면 그 코덱을
|
|
16
|
+
고른 producer 생성과 그 코덱 배치 수신이 설치 안내를 담은 오류로 멈춘다.
|
|
15
17
|
"""
|
|
16
18
|
|
|
17
19
|
from __future__ import annotations
|
|
18
20
|
|
|
19
21
|
import threading
|
|
20
|
-
import zlib
|
|
21
22
|
|
|
22
23
|
from .exception import ILOperationException
|
|
23
24
|
|
|
24
25
|
#: 코덱 번호(배치 헤더 attributes 의 하위 3비트)
|
|
25
26
|
CODEC_NONE = 0
|
|
26
|
-
|
|
27
|
+
CODEC_LZ4 = 3
|
|
27
28
|
CODEC_ZSTD = 4
|
|
28
29
|
#: attributes 에서 코덱을 뽑는 마스크
|
|
29
30
|
CODEC_MASK = 0x07
|
|
30
31
|
#: 설정 이름 -> 코덱 번호
|
|
31
|
-
CODECS = {"none": CODEC_NONE, "
|
|
32
|
+
CODECS = {"none": CODEC_NONE, "lz4": CODEC_LZ4, "zstd": CODEC_ZSTD}
|
|
32
33
|
#: 코덱 번호 -> 설정 이름
|
|
33
34
|
CODEC_NAMES = {v: k for k, v in CODECS.items()}
|
|
34
35
|
|
|
35
|
-
# 레벨의 기본값과 범위는 Kafka compression.
|
|
36
|
-
# 사용자 결정 2026-09-13
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
# 레벨의 기본값과 범위는 Kafka compression.lz4.level / compression.zstd.level 과 같다(kafka-clients 4.3.1 확인,
|
|
37
|
+
# 사용자 결정 2026-09-13 / 2026-09-14): lz4 기본 9 / 1~17, zstd 기본 3 / -131072(ZSTD_minCLevel)~22.
|
|
38
|
+
# lz4 레벨의 뜻도 Kafka 와 같다 - 9 는 빠른 압축기(레벨을 쓰지 않는다), 9 가 아니면 그 레벨의 LZ4HC
|
|
39
|
+
LZ4_LEVEL_DEFAULT = 9
|
|
40
|
+
LZ4_LEVEL_MIN = 1
|
|
41
|
+
LZ4_LEVEL_MAX = 17
|
|
40
42
|
ZSTD_LEVEL_DEFAULT = 3
|
|
41
43
|
ZSTD_LEVEL_MIN = -131072
|
|
42
44
|
ZSTD_LEVEL_MAX = 22
|
|
@@ -44,9 +46,16 @@ ZSTD_LEVEL_MAX = 22
|
|
|
44
46
|
#: 푼 레코드 열의 크기 상한(바이트). 엔진과 같은 128MiB 다. 시험이 낮출 수 있게 모듈 변수로 둔다(호출 때 읽는다)
|
|
45
47
|
MAX_RAW = 128 * 1024 * 1024
|
|
46
48
|
|
|
49
|
+
#: lz4 모듈이 없을 때 오류 문구에 싣는 설치 안내
|
|
50
|
+
LZ4_HINT = 'pip install "jetstream-api[lz4]"'
|
|
47
51
|
#: zstd 모듈이 없을 때 오류 문구에 싣는 설치 안내
|
|
48
52
|
ZSTD_HINT = 'pip install "jetstream-api[zstd]", or use Python 3.14+ (standard library compression.zstd)'
|
|
49
53
|
|
|
54
|
+
#: LZ4 frame 머리의 magic(0x184D2204, 리틀 엔디언)
|
|
55
|
+
_LZ4_MAGIC = bytes((0x04, 0x22, 0x4D, 0x18))
|
|
56
|
+
#: 원문 크기를 믿을 수 없는 lz4 frame 을 조금씩 풀 때 한 번에 받는 출력 크기(바이트) - lz4.frame 은 이만큼을 미리 잡는다
|
|
57
|
+
_LZ4_STEP = 1024 * 1024
|
|
58
|
+
|
|
50
59
|
#: zstandard 백엔드가 한 번에 먹이는 압축 입력 크기(바이트). 그 API 는 출력 상한을 받지 않아 입력을 잘게 먹이며
|
|
51
60
|
#: 출력을 센다 - 넘침은 이 크기가 만들 수 있는 출력만큼으로 묶인다
|
|
52
61
|
_ZSTANDARD_FEED = 4096
|
|
@@ -57,37 +66,35 @@ def check_type(codec) -> str:
|
|
|
57
66
|
"""압축 코덱 이름을 검사한다.
|
|
58
67
|
|
|
59
68
|
Args:
|
|
60
|
-
codec (str): ``"none"`` / ``"
|
|
69
|
+
codec (str): ``"none"`` / ``"lz4"`` / ``"zstd"`` - 소문자 그대로다(``"LZ4"`` 는 받지 않는다).
|
|
61
70
|
|
|
62
71
|
Returns:
|
|
63
72
|
str: 받은 이름 그대로.
|
|
64
73
|
|
|
65
74
|
Raises:
|
|
66
|
-
ValueError: 문자열이 아니거나 세 값 밖일
|
|
75
|
+
ValueError: 문자열이 아니거나 세 값 밖일 때(``"gzip"`` / ``"snappy"`` 포함).
|
|
67
76
|
"""
|
|
68
77
|
# 자바 ILTopicCodec.codecOf 와 Kafka compression.type 처럼 소문자 이름만 받는다(두 언어가 같은 설정을 같게 받는다)
|
|
69
78
|
if not isinstance(codec, str) or codec not in CODECS:
|
|
70
|
-
raise ValueError("compressionType must be none /
|
|
79
|
+
raise ValueError("compressionType must be none / lz4 / zstd (got %r - gzip and snappy are not supported)"
|
|
71
80
|
% (codec,))
|
|
72
81
|
return codec
|
|
73
82
|
|
|
74
83
|
|
|
75
|
-
def
|
|
76
|
-
"""
|
|
84
|
+
def check_lz4_level(level) -> int:
|
|
85
|
+
"""lz4 레벨을 검사한다.
|
|
77
86
|
|
|
78
87
|
Args:
|
|
79
|
-
level (int):
|
|
88
|
+
level (int): ``1`` ~ ``17``. ``9`` (기본)는 빠른 압축기이고, 그 밖의 값은 그 레벨의 LZ4HC 다.
|
|
80
89
|
|
|
81
90
|
Returns:
|
|
82
91
|
int: 받은 레벨 그대로.
|
|
83
92
|
|
|
84
93
|
Raises:
|
|
85
|
-
ValueError: 정수가 아니거나 범위 밖일
|
|
94
|
+
ValueError: 정수가 아니거나 범위 밖일 때.
|
|
86
95
|
"""
|
|
87
|
-
if isinstance(level, bool) or not isinstance(level, int) or not (
|
|
88
|
-
|
|
89
|
-
raise ValueError("compressionGzipLevel must be -1 (zlib default, 6) or %d..%d (got %r)"
|
|
90
|
-
% (GZIP_LEVEL_MIN, GZIP_LEVEL_MAX, level))
|
|
96
|
+
if isinstance(level, bool) or not isinstance(level, int) or not (LZ4_LEVEL_MIN <= level <= LZ4_LEVEL_MAX):
|
|
97
|
+
raise ValueError("compressionLz4Level must be %d..%d (got %r)" % (LZ4_LEVEL_MIN, LZ4_LEVEL_MAX, level))
|
|
91
98
|
return level
|
|
92
99
|
|
|
93
100
|
|
|
@@ -108,34 +115,103 @@ def check_zstd_level(level) -> int:
|
|
|
108
115
|
return level
|
|
109
116
|
|
|
110
117
|
|
|
111
|
-
# ──
|
|
112
|
-
|
|
113
|
-
"""
|
|
118
|
+
# ── lz4 백엔드 ───────────────────────────────────────────────────────────
|
|
119
|
+
class _Lz4:
|
|
120
|
+
"""``lz4`` 패키지의 ``lz4.frame`` 백엔드(``pip install "jetstream-api[lz4]"``)."""
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
data (bytes | bytearray | memoryview): 압축할 바이트.
|
|
117
|
-
level (int): ``-1`` (zlib 기본 레벨 6) 또는 ``1`` ~ ``9``.
|
|
122
|
+
name = "lz4"
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"""
|
|
122
|
-
c = zlib.compressobj(level, zlib.DEFLATED, 31) # wbits 16 + 15 = gzip 틀(엔진 inflateInit2(16 + MAX_WBITS))
|
|
123
|
-
return c.compress(data) + c.flush()
|
|
124
|
+
def __init__(self, frame) -> None:
|
|
125
|
+
self._f = frame
|
|
124
126
|
|
|
127
|
+
def compress(self, data, level: int) -> bytes:
|
|
128
|
+
"""frame 하나로 압축한다(64KB 독립 블록, 원문 크기를 싣고 체크섬은 쓰지 않는다).
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
Args:
|
|
131
|
+
data (bytes | bytearray | memoryview): 압축할 바이트.
|
|
132
|
+
level (int): lz4 레벨(``1`` ~ ``17``). ``9`` 는 빠른 압축기, 그 밖은 그 레벨의 LZ4HC 다.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
bytes: LZ4 frame 하나.
|
|
136
|
+
"""
|
|
137
|
+
f = self._f
|
|
138
|
+
# Kafka Lz4BlockOutputStream 과 같은 뜻 - 기본 9 = 빠른 압축기(lz4.frame 레벨 0), 그 밖은 HC(레벨 그대로).
|
|
139
|
+
# lz4.frame 은 3 이상이 HC 이고 1~2 는 빠른 압축기와 같다(자바 lz4-java 는 1~2 도 HC 계열 - 라이브러리 차이).
|
|
140
|
+
# block_linked 기본값이 True(연결형)라 반드시 끈다 - 자바 lz4-java 는 연결형 frame 을 풀지 못한다
|
|
141
|
+
return f.compress(data, compression_level=0 if level == LZ4_LEVEL_DEFAULT else level,
|
|
142
|
+
block_size=f.BLOCKSIZE_MAX64KB, block_linked=False,
|
|
143
|
+
content_checksum=False, block_checksum=False, store_size=True)
|
|
144
|
+
|
|
145
|
+
def decompress(self, data, limit: int) -> bytes:
|
|
146
|
+
"""이어 붙인 frame 을 모두 푼다. 원문 크기가 없는 frame 도 된다.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
data (bytes | memoryview): LZ4 frame(들).
|
|
150
|
+
limit (int): 푼 크기 상한(바이트).
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
bytes: 푼 바이트.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ValueError: 깨졌거나 잘렸거나 뒤에 남는 바이트가 있거나 상한을 넘을 때(``MIMQE_TOPIC_CODEC_ERROR (lz4: ...)``).
|
|
157
|
+
"""
|
|
158
|
+
f = self._f
|
|
159
|
+
rest = memoryview(data)
|
|
160
|
+
if not len(rest):
|
|
161
|
+
raise ValueError("MIMQE_TOPIC_CODEC_ERROR (lz4: truncated)")
|
|
162
|
+
over = "MIMQE_TOPIC_CODEC_ERROR (lz4: decompressed size over %d bytes)" % limit
|
|
163
|
+
out = []
|
|
164
|
+
total = 0
|
|
165
|
+
frames = 0
|
|
166
|
+
while True:
|
|
167
|
+
# lz4.frame.decompress() 는 첫 frame 만 풀고 뒤 frame / 쓰레기를 조용히 버리므로 쓰지 않는다. 풀개 하나는 frame
|
|
168
|
+
# 하나만 풀고 남은 바이트(unused_data)를 돌려준다 - 다음 frame 은 새 풀개로 푼다.
|
|
169
|
+
# lz4.frame 은 max_length 만큼 출력을 **미리 잡는다**(상한 128MiB 를 그대로 주면 배치마다 128MiB 를 잡는다). 그래서 frame
|
|
170
|
+
# 머리의 원문 크기가 있으면 상한과 먼저 견주고 그 크기로 한 번에 잡는다. 머리 값은 믿을 수 없는 입력이라, 이 frame 에 남은
|
|
171
|
+
# 압축 바이트로 만들 수 있는 크기(lz4 는 약 255배를 넘지 않는다)보다 크면 원문 크기가 없는 frame 처럼 조금씩 푼다
|
|
172
|
+
n = len(rest)
|
|
173
|
+
csize = None
|
|
174
|
+
if n >= 14 and rest[0:4] == _LZ4_MAGIC and rest[4] & 0x08:
|
|
175
|
+
csize = int.from_bytes(rest[6:14], "little")
|
|
176
|
+
if csize > limit - total:
|
|
177
|
+
raise ValueError(over)
|
|
178
|
+
d = f.LZ4FrameDecompressor()
|
|
179
|
+
try:
|
|
180
|
+
if csize is not None and csize <= n * 255 + 65536:
|
|
181
|
+
# 원문 크기와 다르면 lz4 가 오류(ERROR_frameSize_wrong)를 낸다 - +1 은 넘치는 것을 자르지 않고 보게 한다
|
|
182
|
+
parts = [d.decompress(rest, max_length=csize + 1)]
|
|
183
|
+
got = len(parts[0])
|
|
184
|
+
else:
|
|
185
|
+
parts = []
|
|
186
|
+
got = 0
|
|
187
|
+
piece = d.decompress(rest, max_length=_LZ4_STEP)
|
|
188
|
+
while True:
|
|
189
|
+
parts.append(piece)
|
|
190
|
+
got += len(piece)
|
|
191
|
+
if total + got > limit:
|
|
192
|
+
raise ValueError(over)
|
|
193
|
+
if d.eof or d.needs_input:
|
|
194
|
+
break
|
|
195
|
+
piece = d.decompress(b"", max_length=_LZ4_STEP)
|
|
196
|
+
except RuntimeError as exc:
|
|
197
|
+
if frames:
|
|
198
|
+
raise ValueError("MIMQE_TOPIC_CODEC_ERROR (lz4: %d trailing bytes after the lz4 frame - %s)"
|
|
199
|
+
% (len(rest), exc)) from None
|
|
200
|
+
raise ValueError("MIMQE_TOPIC_CODEC_ERROR (lz4: %s)" % exc) from None
|
|
201
|
+
total += got
|
|
202
|
+
if total > limit:
|
|
203
|
+
raise ValueError(over)
|
|
204
|
+
out.extend(parts)
|
|
205
|
+
if not d.eof:
|
|
206
|
+
# 상한까지 풀 자리를 주었으므로 여기서 멈춘 것은 입력이 끝났기 때문이다. 앞 frame 이 끝난 뒤라면 frame 이
|
|
207
|
+
# 되지 못한 뒤 바이트다(엔진처럼 거절)
|
|
208
|
+
if frames:
|
|
209
|
+
raise ValueError("MIMQE_TOPIC_CODEC_ERROR (lz4: %d trailing bytes after the lz4 frame)" % len(rest))
|
|
210
|
+
raise ValueError("MIMQE_TOPIC_CODEC_ERROR (lz4: truncated)")
|
|
211
|
+
frames += 1
|
|
212
|
+
rest = d.unused_data
|
|
213
|
+
if not rest:
|
|
214
|
+
return b"".join(out)
|
|
139
215
|
|
|
140
216
|
|
|
141
217
|
# ── zstd 백엔드 ──────────────────────────────────────────────────────────
|
|
@@ -279,12 +355,22 @@ class _ZstandardZstd:
|
|
|
279
355
|
|
|
280
356
|
|
|
281
357
|
_UNPROBED = object()
|
|
282
|
-
#: zstd 백엔드. _UNPROBED = 아직 찾지 않음, None = 없음. 시험이 가짜 백엔드로 바꿔 끼울 수 있다
|
|
358
|
+
#: lz4 / zstd 백엔드. _UNPROBED = 아직 찾지 않음, None = 없음. 시험이 가짜 백엔드로 바꿔 끼울 수 있다
|
|
359
|
+
_lz4_state = _UNPROBED
|
|
283
360
|
_zstd_state = _UNPROBED
|
|
284
361
|
_probe_lock = threading.Lock()
|
|
285
362
|
|
|
286
363
|
|
|
287
|
-
def
|
|
364
|
+
def _probe_lz4():
|
|
365
|
+
"""``lz4.frame`` 을 찾는다. 없으면 None."""
|
|
366
|
+
try:
|
|
367
|
+
import lz4.frame as frame # type: ignore[import-not-found]
|
|
368
|
+
except ImportError:
|
|
369
|
+
return None
|
|
370
|
+
return _Lz4(frame)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _probe_zstd():
|
|
288
374
|
"""표준 라이브러리(3.14+) -> zstandard 순으로 찾는다. 없으면 None."""
|
|
289
375
|
try:
|
|
290
376
|
from compression import zstd as mod # type: ignore[import-not-found]
|
|
@@ -299,6 +385,22 @@ def _probe():
|
|
|
299
385
|
return _ZstandardZstd(mod2)
|
|
300
386
|
|
|
301
387
|
|
|
388
|
+
def lz4_backend():
|
|
389
|
+
"""lz4 백엔드를 돌려준다. 처음 부를 때 찾는다.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
object | None: ``compress(data, level)`` / ``decompress(data, limit)`` 를 가진 백엔드. 없으면 None.
|
|
393
|
+
"""
|
|
394
|
+
global _lz4_state
|
|
395
|
+
s = _lz4_state
|
|
396
|
+
if s is _UNPROBED:
|
|
397
|
+
with _probe_lock:
|
|
398
|
+
if _lz4_state is _UNPROBED:
|
|
399
|
+
_lz4_state = _probe_lz4()
|
|
400
|
+
s = _lz4_state
|
|
401
|
+
return s
|
|
402
|
+
|
|
403
|
+
|
|
302
404
|
def zstd_backend():
|
|
303
405
|
"""zstd 백엔드를 돌려준다. 처음 부를 때 찾는다.
|
|
304
406
|
|
|
@@ -310,18 +412,18 @@ def zstd_backend():
|
|
|
310
412
|
if s is _UNPROBED:
|
|
311
413
|
with _probe_lock:
|
|
312
414
|
if _zstd_state is _UNPROBED:
|
|
313
|
-
_zstd_state =
|
|
415
|
+
_zstd_state = _probe_zstd()
|
|
314
416
|
s = _zstd_state
|
|
315
417
|
return s
|
|
316
418
|
|
|
317
419
|
|
|
318
420
|
# ── 발행 / 소비 쪽 입구 ───────────────────────────────────────────────────
|
|
319
|
-
def compressor(codec: str,
|
|
421
|
+
def compressor(codec: str, lz4_level: int, zstd_level: int):
|
|
320
422
|
"""producer 가 쓸 (코덱 번호, 압축 함수) 를 만든다. 압축 함수는 레코드 열 바이트를 받아 압축한 bytes 를 돌려준다.
|
|
321
423
|
|
|
322
424
|
Args:
|
|
323
|
-
codec (str): ``"none"`` / ``"
|
|
324
|
-
|
|
425
|
+
codec (str): ``"none"`` / ``"lz4"`` / ``"zstd"``.
|
|
426
|
+
lz4_level (int): lz4 레벨(``1`` ~ ``17``).
|
|
325
427
|
zstd_level (int): zstd 레벨(``-131072`` ~ ``22``).
|
|
326
428
|
|
|
327
429
|
Returns:
|
|
@@ -329,14 +431,17 @@ def compressor(codec: str, gzip_level: int, zstd_level: int):
|
|
|
329
431
|
|
|
330
432
|
Raises:
|
|
331
433
|
ValueError: 코덱 이름이나 레벨이 틀렸을 때.
|
|
332
|
-
ILOperationException:
|
|
434
|
+
ILOperationException: 고른 코덱의 모듈이 없을 때 - ``MIMQE_NOT_SUPPORTED (...)`` 에 설치 안내를 싣는다.
|
|
333
435
|
"""
|
|
334
436
|
c = CODECS[check_type(codec)]
|
|
335
437
|
if c == CODEC_NONE:
|
|
336
438
|
return CODEC_NONE, None
|
|
337
|
-
if c ==
|
|
338
|
-
|
|
339
|
-
|
|
439
|
+
if c == CODEC_LZ4:
|
|
440
|
+
llv = check_lz4_level(lz4_level)
|
|
441
|
+
lbe = lz4_backend()
|
|
442
|
+
if lbe is None:
|
|
443
|
+
raise ILOperationException("MIMQE_NOT_SUPPORTED (compressionType lz4: no lz4 module - %s)" % LZ4_HINT)
|
|
444
|
+
return CODEC_LZ4, lambda data: lbe.compress(data, llv)
|
|
340
445
|
zlv = check_zstd_level(zstd_level)
|
|
341
446
|
be = zstd_backend()
|
|
342
447
|
if be is None:
|
|
@@ -348,20 +453,23 @@ def decompress(codec: int, data) -> bytes:
|
|
|
348
453
|
"""배치 하나의 레코드 열을 푼다. 푼 크기는 :data:`MAX_RAW` 까지다.
|
|
349
454
|
|
|
350
455
|
Args:
|
|
351
|
-
codec (int): 배치 헤더 attributes 의 하위 3비트(``
|
|
456
|
+
codec (int): 배치 헤더 attributes 의 하위 3비트(``3`` lz4 / ``4`` zstd).
|
|
352
457
|
data (bytes | memoryview): 압축된 레코드 열(``[52, batchLength)``).
|
|
353
458
|
|
|
354
459
|
Returns:
|
|
355
460
|
bytes: 푼 레코드 열.
|
|
356
461
|
|
|
357
462
|
Raises:
|
|
358
|
-
ValueError: 모르는 코덱(``MIMQE_TOPIC_UNSUPPORTED_CODEC (codec N)``),
|
|
463
|
+
ValueError: 모르는 코덱(``MIMQE_TOPIC_UNSUPPORTED_CODEC (codec N)``), 코덱 모듈 없음(``MIMQE_NOT_SUPPORTED``,
|
|
359
464
|
설치 안내를 싣는다),
|
|
360
465
|
깨졌거나 잘렸거나 뒤에 남는 바이트가 있거나 상한을 넘을 때(``MIMQE_TOPIC_CODEC_ERROR (...)``).
|
|
361
466
|
"""
|
|
362
467
|
limit = MAX_RAW
|
|
363
|
-
if codec ==
|
|
364
|
-
|
|
468
|
+
if codec == CODEC_LZ4:
|
|
469
|
+
lbe = lz4_backend()
|
|
470
|
+
if lbe is None:
|
|
471
|
+
raise ValueError("MIMQE_NOT_SUPPORTED (codec 3 lz4: no lz4 module - %s)" % LZ4_HINT)
|
|
472
|
+
return lbe.decompress(data, limit)
|
|
365
473
|
if codec == CODEC_ZSTD:
|
|
366
474
|
be = zstd_backend()
|
|
367
475
|
if be is None:
|
|
@@ -617,7 +617,7 @@ class ILTopicBatchV4Msg(ILInnerMsg):
|
|
|
617
617
|
i32 batchCount | v4 배치 x batchCount``. 배치는 발행 v2 의 파티션 배치와 같은 형식(52바이트 헤더 + 레코드)이고
|
|
618
618
|
엔진이 baseOffset / timestamp 를 채웠다. 레코드 오프셋은 baseOffset + 순번, 시각은 배치 timestamp 다.
|
|
619
619
|
|
|
620
|
-
0.8.0: 배치는 압축돼 있을 수 있다(헤더 attributes 하위 3비트 = 코덱: 0 없음 /
|
|
620
|
+
0.8.0: 배치는 압축돼 있을 수 있다(헤더 attributes 하위 3비트 = 코덱: 0 없음 / 3 lz4 / 4 zstd). 엔진은 저장한
|
|
621
621
|
바이트를 그대로 보내므로 배치마다 코덱을 보고 레코드 열을 푼다 - 한 응답에 코덱이 섞여도 된다.
|
|
622
622
|
"""
|
|
623
623
|
|
|
@@ -632,7 +632,7 @@ class ILTopicBatchV4Msg(ILInnerMsg):
|
|
|
632
632
|
def records(self) -> list:
|
|
633
633
|
"""레코드를 배치 순서대로 푼다. 파티션의 startOffset 보다 앞선 레코드는 뺀다(첫 배치는 커서 앞 레코드를
|
|
634
634
|
담을 수 있다). 배치 CRC(범위 [28, batchLength), 압축 배치면 압축한 바이트)를 검증하고, 압축 배치는 레코드 열을
|
|
635
|
-
풀어서 읽는다(0.8.0
|
|
635
|
+
풀어서 읽는다(0.8.0, 0.9.0 부터 lz4 / zstd - 푼 크기 128MiB 까지).
|
|
636
636
|
|
|
637
637
|
Returns:
|
|
638
638
|
list: ``(partition, offset, timestamp, key, value, property_bytes)`` 튜플 목록. key 는 없으면 ``""``,
|
|
@@ -57,7 +57,7 @@ class ILProducerConfig:
|
|
|
57
57
|
"acks", "lingerMs", "batchSize", "maxInFlight", "retries",
|
|
58
58
|
"deliveryTimeoutMs", "requestTimeoutMs", "retryBackoffMs",
|
|
59
59
|
"bufferMemory", "maxBlockMs", "maxRequestSize", "maxFrameLength", "enableIdempotence",
|
|
60
|
-
"compressionType", "
|
|
60
|
+
"compressionType", "compressionLz4Level", "compressionZstdLevel",
|
|
61
61
|
))
|
|
62
62
|
|
|
63
63
|
def __init__(self) -> None:
|
|
@@ -91,11 +91,12 @@ class ILProducerConfig:
|
|
|
91
91
|
self._enableIdempotence = True
|
|
92
92
|
#: 멱등을 사용자가 정했는지. 정하지 않았으면 acks=0 이나 maxInFlight > 5 에서 저절로 꺼진다
|
|
93
93
|
self._idempotenceSet = False
|
|
94
|
-
# 0.8.0: 배치 압축. 이름과 기본값이 Kafka compression.type / compression.
|
|
95
|
-
|
|
94
|
+
# 0.8.0: 배치 압축. 이름과 기본값이 Kafka compression.type / compression.lz4.level / compression.zstd.level 과 같다
|
|
95
|
+
# (0.9.0: gzip 을 빼고 lz4 를 넣었다 - 사용자 결정 2026-09-14)
|
|
96
|
+
#: 파티션 배치 압축 코덱 "none" / "lz4" / "zstd". 기본 "none"
|
|
96
97
|
self._compressionType = "none"
|
|
97
|
-
#:
|
|
98
|
-
self.
|
|
98
|
+
#: lz4 레벨 1~17. 기본 9(= 빠른 압축기, 9 가 아니면 그 레벨의 LZ4HC)
|
|
99
|
+
self._compressionLz4Level = _codec.LZ4_LEVEL_DEFAULT
|
|
99
100
|
#: zstd 레벨 -131072~22. 기본 3
|
|
100
101
|
self._compressionZstdLevel = _codec.ZSTD_LEVEL_DEFAULT
|
|
101
102
|
|
|
@@ -368,16 +369,17 @@ class ILProducerConfig:
|
|
|
368
369
|
"""파티션 배치의 압축 코덱을 정한다. (기본 ``"none"`` - 압축하지 않는다)
|
|
369
370
|
|
|
370
371
|
Args:
|
|
371
|
-
codec (str): ``"none"`` / ``"
|
|
372
|
-
``"
|
|
373
|
-
``pip install "jetstream-api[
|
|
374
|
-
``
|
|
372
|
+
codec (str): ``"none"`` / ``"lz4"`` / ``"zstd"`` - 소문자 그대로 쓴다(``"LZ4"`` 는 받지 않는다).
|
|
373
|
+
클라이언트 CPU·지연이 중요하면 ``"lz4"``, 디스크·용량이 중요하면 ``"zstd"`` 를 고른다.
|
|
374
|
+
``"lz4"`` 는 lz4 모듈이 있어야 한다 - ``pip install "jetstream-api[lz4]"``.
|
|
375
|
+
``"zstd"`` 는 zstd 모듈이 있어야 한다 - ``pip install "jetstream-api[zstd]"`` 로 설치하거나,
|
|
376
|
+
파이썬 3.14 이상이면 표준 라이브러리 ``compression.zstd`` 를 먼저 쓴다.
|
|
375
377
|
|
|
376
378
|
Returns:
|
|
377
379
|
ILProducerConfig: 체이닝을 위해 자기 자신.
|
|
378
380
|
|
|
379
381
|
Raises:
|
|
380
|
-
ValueError: 세 값 밖일 때(``"
|
|
382
|
+
ValueError: 세 값 밖일 때(``"gzip"`` / ``"snappy"`` 는 지원하지 않는다).
|
|
381
383
|
|
|
382
384
|
Note:
|
|
383
385
|
파티션 배치를 보낼 때 레코드 열만 **한 번** 압축한다(배치 헤더 52바이트는 그대로). 재전송은 같은 압축
|
|
@@ -386,23 +388,21 @@ class ILProducerConfig:
|
|
|
386
388
|
세고, 봉투에 배치를 채울 때(:meth:`maxRequestSize`)는 압축한 **뒤** 길이로 센다. 압축은 producer 의
|
|
387
389
|
I/O 스레드가 한다.
|
|
388
390
|
|
|
389
|
-
|
|
390
|
-
압축 배치를 받는 구독자도 0.8.0 이상이어야 한다(엔진은 배치 read 에 저장한 바이트를 풀지 않고 보낸다).
|
|
391
|
-
옛 판 중에는 엔진 단건 read 를 쓰는 0.6.x 의 ``read()`` 만 압축 토픽을 읽는다(0.7.x 는 ``read()`` 도 배치 read 라서
|
|
392
|
-
형식 오류로 멈춘다).
|
|
391
|
+
구독자는 받은 압축 배치를 스스로 푼다(엔진은 배치 read 에 저장한 바이트를 풀지 않고 보낸다).
|
|
393
392
|
|
|
394
|
-
zstd 를 골랐는데
|
|
395
|
-
(``MIMQE_NOT_SUPPORTED``, 설치 안내를 싣는다)으로 멈춘다.
|
|
393
|
+
lz4 / zstd 를 골랐는데 그 모듈이 없으면 producer 생성이 연결하기 전에 ``ILOperationException``
|
|
394
|
+
(``MIMQE_NOT_SUPPORTED``, 설치 안내를 싣는다)으로 멈춘다. 구독자도 그 코덱의 배치를 받으면 같은 사유로
|
|
395
|
+
그 파티션에서 멈춘다 - 발행하는 코덱의 모듈을 구독하는 쪽에도 설치한다.
|
|
396
396
|
"""
|
|
397
397
|
self._compressionType = _codec.check_type(codec)
|
|
398
398
|
return self
|
|
399
399
|
|
|
400
|
-
def
|
|
401
|
-
"""
|
|
400
|
+
def compressionLz4Level(self, level: int) -> "ILProducerConfig":
|
|
401
|
+
"""lz4 압축 레벨을 정한다. (기본 ``9`` = 빠른 압축기)
|
|
402
402
|
|
|
403
403
|
Args:
|
|
404
|
-
level (int):
|
|
405
|
-
|
|
404
|
+
level (int): ``1`` ~ ``17``. Kafka ``compression.lz4.level`` 과 뜻이 같다 - ``9`` (기본)는 빠른 압축기이고,
|
|
405
|
+
``9`` 가 아닌 값은 그 레벨의 LZ4HC(훨씬 느리고 조금 더 준다)다. 레벨 12 이상은 결과가 같다.
|
|
406
406
|
|
|
407
407
|
Returns:
|
|
408
408
|
ILProducerConfig: 체이닝을 위해 자기 자신.
|
|
@@ -411,9 +411,10 @@ class ILProducerConfig:
|
|
|
411
411
|
ValueError: 정수가 아니거나 범위 밖일 때.
|
|
412
412
|
|
|
413
413
|
Note:
|
|
414
|
-
:meth:`compressionType` 이 ``"
|
|
414
|
+
:meth:`compressionType` 이 ``"lz4"`` 일 때만 쓰인다. 파이썬 lz4 모듈은 레벨 1~2 를 빠른 압축기와 같게
|
|
415
|
+
다룬다(자바 lz4-java 는 1~2 도 HC 계열이다 - 라이브러리 차이). 빠른 lz4 를 쓰려면 기본값 9 를 그대로 둔다.
|
|
415
416
|
"""
|
|
416
|
-
self.
|
|
417
|
+
self._compressionLz4Level = _codec.check_lz4_level(level)
|
|
417
418
|
return self
|
|
418
419
|
|
|
419
420
|
def compressionZstdLevel(self, level: int) -> "ILProducerConfig":
|
|
@@ -496,9 +497,9 @@ class ILProducerConfig:
|
|
|
496
497
|
""":meth:`compressionType` 의 snake 별칭."""
|
|
497
498
|
return self.compressionType(codec)
|
|
498
499
|
|
|
499
|
-
def
|
|
500
|
-
""":meth:`
|
|
501
|
-
return self.
|
|
500
|
+
def compression_lz4_level(self, level: int) -> "ILProducerConfig":
|
|
501
|
+
""":meth:`compressionLz4Level` 의 snake 별칭."""
|
|
502
|
+
return self.compressionLz4Level(level)
|
|
502
503
|
|
|
503
504
|
def compression_zstd_level(self, level: int) -> "ILProducerConfig":
|
|
504
505
|
""":meth:`compressionZstdLevel` 의 snake 별칭."""
|
|
@@ -616,17 +617,17 @@ class ILProducerConfig:
|
|
|
616
617
|
"""파티션 배치 압축 코덱. 기본 ``"none"``.
|
|
617
618
|
|
|
618
619
|
Returns:
|
|
619
|
-
str: ``"none"`` / ``"
|
|
620
|
+
str: ``"none"`` / ``"lz4"`` / ``"zstd"`` (소문자).
|
|
620
621
|
"""
|
|
621
622
|
return self._compressionType
|
|
622
623
|
|
|
623
|
-
def
|
|
624
|
-
"""
|
|
624
|
+
def get_compression_lz4_level(self) -> int:
|
|
625
|
+
"""lz4 압축 레벨. 기본 9(빠른 압축기).
|
|
625
626
|
|
|
626
627
|
Returns:
|
|
627
|
-
int:
|
|
628
|
+
int: ``1`` ~ ``17``.
|
|
628
629
|
"""
|
|
629
|
-
return self.
|
|
630
|
+
return self._compressionLz4Level
|
|
630
631
|
|
|
631
632
|
def get_compression_zstd_level(self) -> int:
|
|
632
633
|
"""zstd 압축 레벨. 기본 3.
|
|
@@ -650,7 +651,7 @@ class ILProducerConfig:
|
|
|
650
651
|
getMaxFrameLength = get_max_frame_length
|
|
651
652
|
isIdempotenceEnabled = is_idempotence_enabled
|
|
652
653
|
getCompressionType = get_compression_type
|
|
653
|
-
|
|
654
|
+
getCompressionLz4Level = get_compression_lz4_level
|
|
654
655
|
getCompressionZstdLevel = get_compression_zstd_level
|
|
655
656
|
|
|
656
657
|
def validate(self) -> None:
|
|
@@ -671,9 +672,9 @@ class ILProducerConfig:
|
|
|
671
672
|
"enableIdempotence requires maxInFlight <= 5 (the engine remembers the last 5 batches"
|
|
672
673
|
" per partition; Kafka has the same limit)"
|
|
673
674
|
)
|
|
674
|
-
# 0.8.0: 직접 대입(cfg.compressionType = "
|
|
675
|
+
# 0.8.0: 직접 대입(cfg.compressionType = "gzip")은 빌더의 검사를 거치지 않는다 - 여기서 다시 본다
|
|
675
676
|
self._compressionType = _codec.check_type(self._compressionType)
|
|
676
|
-
_codec.
|
|
677
|
+
_codec.check_lz4_level(self._compressionLz4Level)
|
|
677
678
|
_codec.check_zstd_level(self._compressionZstdLevel)
|
|
678
679
|
|
|
679
680
|
|
|
@@ -1295,7 +1296,7 @@ class ILTopicProducer:
|
|
|
1295
1296
|
self.config.validate()
|
|
1296
1297
|
cfg = self.config
|
|
1297
1298
|
# 0.8.0: 배치 압축 - (코덱 번호, 압축 함수). none 이면 압축 함수가 None 이다. zstd 모듈이 없으면 연결하기 전에 여기서 멈춘다
|
|
1298
|
-
self._codec_id, self._compress = _codec.compressor(cfg.get_compression_type(), cfg.
|
|
1299
|
+
self._codec_id, self._compress = _codec.compressor(cfg.get_compression_type(), cfg.get_compression_lz4_level(),
|
|
1299
1300
|
cfg.get_compression_zstd_level())
|
|
1300
1301
|
self.topic = topic
|
|
1301
1302
|
self._topic_bytes = topic.encode("utf-8")
|
|
@@ -731,7 +731,7 @@ class ILTopicSubscription:
|
|
|
731
731
|
|
|
732
732
|
.. note:: 풀지 못한 배치는 그 자리에서 멈춘다 (0.8.0)
|
|
733
733
|
|
|
734
|
-
read 응답의 배치를 풀지 못하면(zstd 모듈 없음, 모르는 코덱, 깨진
|
|
734
|
+
read 응답의 배치를 풀지 못하면(lz4 / zstd 모듈 없음, 모르는 코덱, 깨진 lz4 / zstd 스트림, 배치 CRC 불일치, 레코드 수 /
|
|
735
735
|
길이 불일치) 응답 전체를 버리지 않고 배치마다 가른다. 그 배치(파티션 P, baseOffset B)보다 앞에 푼 레코드와 같은
|
|
736
736
|
응답의 다른 파티션 레코드는 여느 때처럼 준다(``COMMIT_AUTO`` 커밋은 앱에 준 것만). 같은 응답에 실린 P 의 뒤 배치는
|
|
737
737
|
버리고 P 는 이 구독의 연결로 B 로 되감는다(``offset:B;partition:P`` seek - 커서가 B 보다 뒤면 커서로). 그래서 원인이
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jetstream-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -40,6 +40,8 @@ Classifier: Topic :: System :: Networking
|
|
|
40
40
|
Requires-Python: >=3.9
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
License-File: LICENSE.txt
|
|
43
|
+
Provides-Extra: lz4
|
|
44
|
+
Requires-Dist: lz4>=4.0; extra == "lz4"
|
|
43
45
|
Provides-Extra: zstd
|
|
44
46
|
Requires-Dist: zstandard>=0.22; extra == "zstd"
|
|
45
47
|
Provides-Extra: test
|
|
@@ -52,11 +54,35 @@ Python으로 작성된 ILink 클라이언트 API입니다.
|
|
|
52
54
|
|
|
53
55
|
## 시스템 요구 사항
|
|
54
56
|
시스템에 Python 3.9 이상, pip가 설치되어 있어야 합니다. 필수 의존성은 없습니다(표준 라이브러리만 씁니다).
|
|
55
|
-
|
|
57
|
+
lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치 압축을 쓰려면 `pip install "jetstream-api[zstd]"` 로
|
|
58
|
+
설치합니다(zstd 는 Python 3.14 이상이면 표준 라이브러리로 됩니다). 둘 다 쓰려면 `pip install "jetstream-api[lz4,zstd]"` 입니다.
|
|
56
59
|
|
|
57
60
|
## 설치 방법
|
|
58
61
|
pip install jetstream-api
|
|
59
62
|
|
|
63
|
+
## 0.9.0 변경 - 압축 코덱 lz4 / zstd, gzip 제거 (자바 v2.5.0 과 같음)
|
|
64
|
+
|
|
65
|
+
- **코덱은 `none` / `lz4` / `zstd` 입니다**: gzip 을 빼고 lz4 를 넣었습니다(사용자 결정). 클라이언트 CPU·지연이 중요하면
|
|
66
|
+
`lz4`, 디스크·용량이 중요하면 `zstd` 를 고르세요. 기본값은 여전히 압축하지 않음(`none`)입니다.
|
|
67
|
+
- **공개 API 제거**: `compressionGzipLevel()` / `compression_gzip_level()` / `get_compression_gzip_level()` /
|
|
68
|
+
`getCompressionGzipLevel()`. `compressionType("gzip")` 은 `ValueError`
|
|
69
|
+
(`compressionType must be none / lz4 / zstd (got 'gzip' - gzip and snappy are not supported)`)입니다.
|
|
70
|
+
- **추가**: `compressionType("lz4")`, `compressionLz4Level(n)` (`compression_lz4_level`) - `1`~`17`, 기본 `9`, 게터
|
|
71
|
+
`get_compression_lz4_level()` / `getCompressionLz4Level()`. 뜻은 Kafka `compression.lz4.level` 과 같습니다 - `9`(기본)는
|
|
72
|
+
빠른 압축기이고, `9` 가 아닌 값은 그 레벨의 LZ4HC(훨씬 느리고 조금 더 줄입니다)입니다. 레벨 12 이상은 결과가 같습니다.
|
|
73
|
+
파이썬 lz4 모듈은 레벨 1~2 를 빠른 압축기와 같게 다룹니다(자바 lz4-java 는 1~2 도 HC 계열 - 라이브러리 차이).
|
|
74
|
+
- **lz4 는 선택 설치입니다**: `pip install "jetstream-api[lz4]"`(`lz4>=4.0`). 모듈이 없으면 `compressionType("lz4")` producer
|
|
75
|
+
생성이 연결하기 전에 `ILOperationException`(`MIMQE_NOT_SUPPORTED (compressionType lz4: no lz4 module - pip install
|
|
76
|
+
"jetstream-api[lz4]")`)으로 멈추고, lz4 배치를 받은 구독 read 는 원인 `MIMQE_NOT_SUPPORTED (codec 3 lz4: no lz4 module - ...)`
|
|
77
|
+
로 그 배치에서 멈춥니다(zstd 모듈 없음과 같은 `MIMQC_USER_FAULT` / `MIMQE_NOT_SUPPORTED`). 발행하는 코덱의 모듈을 구독하는
|
|
78
|
+
쪽에도 설치하세요.
|
|
79
|
+
- **lz4 와이어**: 배치 attributes 코덱 3, 표준 LZ4 frame(`lz4` 명령과 같은 틀)입니다. producer 는 64KB **독립 블록** + 원문
|
|
80
|
+
크기로 만들고 체크섬은 넣지 않습니다(배치 CRC 가 압축 바이트를 덮습니다) - 자바 v2.5.0 도 같은 틀로 만듭니다. 소비는
|
|
81
|
+
이어 붙인 frame 을 모두 풀고, 잘림 / frame 뒤에 남은 바이트 / 푼 크기 128MiB 초과 / 깨진 frame 은
|
|
82
|
+
`MIMQE_TOPIC_CODEC_ERROR (lz4: ...)` 로 그 배치에서 멈춥니다(0.8.0 의 "풀지 못한 배치" 규칙 그대로).
|
|
83
|
+
- 압축은 예전처럼 producer 의 I/O 스레드가 하고, 줄지 않는 배치는 압축하지 않고 보냅니다. lz4 모듈도 압축하는 동안 GIL 을
|
|
84
|
+
놓습니다.
|
|
85
|
+
|
|
60
86
|
## 0.8.0 변경 - 배치 압축 gzip / zstd (자바 v2.4.0 과 같음)
|
|
61
87
|
|
|
62
88
|
- **엔진 요구**: 압축 배치는 **엔진 7.0.1.3419 이상**(CMPR-1)에서만 받습니다. 그보다 옛 엔진은 압축 배치를 알아보지 못해
|
|
@@ -109,11 +135,6 @@ pip install jetstream-api
|
|
|
109
135
|
떼어 내지 않고 그 파티션만 되감으며, 같은 응답의 다른 토픽 레코드는 그대로 줍니다.
|
|
110
136
|
- 패턴 구독 `read_batch` 는 요청 실패(요청 전체 거절 / 통신 실패)에서도 이 호출에서 이미 모은 레코드를 돌려주고 오류는 다음
|
|
111
137
|
호출이 올립니다(예전 판은 모은 레코드를 버렸는데 AUTO 커밋 좌표는 이미 병합돼 있어 앱이 못 본 레코드가 커밋됐습니다).
|
|
112
|
-
- **옛 클라이언트**: 엔진은 배치 read 에 저장한 바이트를 그대로 보내므로, 압축 토픽을 소비하는 구독자는 **0.8.0 / 자바 v2.4.0
|
|
113
|
-
이상**이어야 합니다. 압축을 켜기 전에 그 토픽의 소비자를 먼저 올리세요. 옛 판 중 압축 토픽을 읽는 것은 엔진 단건 read 를
|
|
114
|
-
쓰는 판뿐입니다 - 파이썬 0.6.x 의 `read()`, 자바 v2.2.x 의 prefetch 를 끈 `read()`(엔진이 풀어서 줍니다). 파이썬 0.7.x 와
|
|
115
|
-
자바 v2.3.x 는 `read()` 도 배치 read 라서 압축 배치에서 형식 오류(`MIMQC_FATAL_ERROR (MIMQE_INTERNAL_ERROR)`, 원인
|
|
116
|
-
`truncated topic batch ...`)로 멈춥니다 - 조용히 틀린 레코드를 주지는 않습니다(pv2test 엔진 7.0.1.3419 실측).
|
|
117
138
|
- **NIO 커넥터 송신은 기한 하나를 씁니다**(`TCPConnectorNIO`, `set_conn` 으로 고른 경우만): 송신 한 번 전체가 접속 대기 시간
|
|
118
139
|
(최소 1 s)을 기한으로 씁니다. 기한이 지나면 `MIMQE_SESSION_TIMEOUT`(`write waiting time has exceeded the time limit : [Nms]
|
|
119
140
|
/ written : [n / 전체]`)을 내고 연결을 닫습니다. 전에는 보낼 자리가 날 때까지 기다릴 때마다 한도를 새로 주어, 상대가
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jetstream-api"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.9.0"
|
|
8
8
|
description = "Python client API for iLink M.O.M. (Message Oriented Middleware)"
|
|
9
9
|
authors = [{name = "snowjeans"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -24,6 +24,8 @@ classifiers = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.optional-dependencies]
|
|
27
|
+
# 0.9.0: lz4 배치 압축(compressionType("lz4") 발행 / lz4 배치 수신). 없으면 lz4 를 쓸 때만 오류가 난다.
|
|
28
|
+
lz4 = ["lz4>=4.0"]
|
|
27
29
|
# 0.8.0: zstd 배치 압축(compressionType("zstd") 발행 / zstd 배치 수신). 파이썬 3.14+ 는 표준 라이브러리 compression.zstd 를
|
|
28
30
|
# 먼저 쓰므로 이 extra 가 없어도 된다. 둘 다 없으면 zstd 를 쓸 때만 오류가 난다 - 런타임 필수 의존성은 여전히 없다.
|
|
29
31
|
zstd = ["zstandard>=0.22"]
|
|
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
|