Init indexed file

This commit is contained in:
Eli-Class
2026-01-27 02:30:42 +00:00
commit 1d13e9fc3e
22 changed files with 2812 additions and 0 deletions

396
CUSTOM_SERIALIZER.md Normal file
View File

@@ -0,0 +1,396 @@
가변 길이 배열이 포함된 데이터의 커스텀 바이너리 직렬화 방법을 보여드릴게요.
```typescript
// 가변 배열 직렬화 예시
import { createSerializer, DataWriter, DataReader } from './src/data-file/index.js';
// ============================================
// 1. 단순 배열 (숫자 배열)
// ============================================
interface SensorReading {
sensorId: number;
values: number[]; // 가변 길이
}
const sensorSerializer = createSerializer<SensorReading>(
(data) => {
// 4(sensorId) + 4(배열길이) + 8*N(values)
const buf = Buffer.alloc(4 + 4 + data.values.length * 8);
let offset = 0;
buf.writeUInt32LE(data.sensorId, offset); offset += 4;
buf.writeUInt32LE(data.values.length, offset); offset += 4;
for (const v of data.values) {
buf.writeDoubleLE(v, offset); offset += 8;
}
return buf;
},
(buf) => {
let offset = 0;
const sensorId = buf.readUInt32LE(offset); offset += 4;
const len = buf.readUInt32LE(offset); offset += 4;
const values: number[] = [];
for (let i = 0; i < len; i++) {
values.push(buf.readDoubleLE(offset)); offset += 8;
}
return { sensorId, values };
}
);
// ============================================
// 2. 객체 배열
// ============================================
interface OrderItem {
sku: string;
qty: number;
price: number;
}
interface Order {
orderId: number;
items: OrderItem[]; // 가변 길이 객체 배열
total: number;
}
const orderSerializer = createSerializer<Order>(
(data) => {
// 각 item의 sku 길이를 먼저 계산
const skuBuffers = data.items.map(item => Buffer.from(item.sku, 'utf8'));
const itemsSize = skuBuffers.reduce(
(sum, skuBuf, i) => sum + 4 + skuBuf.length + 4 + 8, // skuLen + sku + qty + price
0
);
// 4(orderId) + 4(items길이) + itemsSize + 8(total)
const buf = Buffer.alloc(4 + 4 + itemsSize + 8);
let offset = 0;
buf.writeUInt32LE(data.orderId, offset); offset += 4;
buf.writeUInt32LE(data.items.length, offset); offset += 4;
for (let i = 0; i < data.items.length; i++) {
const item = data.items[i];
const skuBuf = skuBuffers[i];
buf.writeUInt32LE(skuBuf.length, offset); offset += 4;
skuBuf.copy(buf, offset); offset += skuBuf.length;
buf.writeUInt32LE(item.qty, offset); offset += 4;
buf.writeDoubleLE(item.price, offset); offset += 8;
}
buf.writeDoubleLE(data.total, offset);
return buf;
},
(buf) => {
let offset = 0;
const orderId = buf.readUInt32LE(offset); offset += 4;
const itemsLen = buf.readUInt32LE(offset); offset += 4;
const items: OrderItem[] = [];
for (let i = 0; i < itemsLen; i++) {
const skuLen = buf.readUInt32LE(offset); offset += 4;
const sku = buf.toString('utf8', offset, offset + skuLen); offset += skuLen;
const qty = buf.readUInt32LE(offset); offset += 4;
const price = buf.readDoubleLE(offset); offset += 8;
items.push({ sku, qty, price });
}
const total = buf.readDoubleLE(offset);
return { orderId, items, total };
}
);
// ============================================
// 3. 다중 가변 배열
// ============================================
interface TimeSeries {
id: number;
timestamps: bigint[]; // 가변
values: number[]; // 가변
tags: string[]; // 가변
}
const timeSeriesSerializer = createSerializer<TimeSeries>(
(data) => {
const tagBuffers = data.tags.map(t => Buffer.from(t, 'utf8'));
const tagsSize = tagBuffers.reduce((sum, b) => sum + 4 + b.length, 0);
// 4(id) + 4(tsLen) + 8*N + 4(valLen) + 8*M + 4(tagLen) + tagsSize
const size = 4 + 4 + data.timestamps.length * 8 + 4 + data.values.length * 8 + 4 + tagsSize;
const buf = Buffer.alloc(size);
let offset = 0;
// id
buf.writeUInt32LE(data.id, offset); offset += 4;
// timestamps
buf.writeUInt32LE(data.timestamps.length, offset); offset += 4;
for (const ts of data.timestamps) {
buf.writeBigUInt64LE(ts, offset); offset += 8;
}
// values
buf.writeUInt32LE(data.values.length, offset); offset += 4;
for (const v of data.values) {
buf.writeDoubleLE(v, offset); offset += 8;
}
// tags
buf.writeUInt32LE(data.tags.length, offset); offset += 4;
for (const tagBuf of tagBuffers) {
buf.writeUInt32LE(tagBuf.length, offset); offset += 4;
tagBuf.copy(buf, offset); offset += tagBuf.length;
}
return buf;
},
(buf) => {
let offset = 0;
const id = buf.readUInt32LE(offset); offset += 4;
// timestamps
const tsLen = buf.readUInt32LE(offset); offset += 4;
const timestamps: bigint[] = [];
for (let i = 0; i < tsLen; i++) {
timestamps.push(buf.readBigUInt64LE(offset)); offset += 8;
}
// values
const valLen = buf.readUInt32LE(offset); offset += 4;
const values: number[] = [];
for (let i = 0; i < valLen; i++) {
values.push(buf.readDoubleLE(offset)); offset += 8;
}
// tags
const tagLen = buf.readUInt32LE(offset); offset += 4;
const tags: string[] = [];
for (let i = 0; i < tagLen; i++) {
const len = buf.readUInt32LE(offset); offset += 4;
tags.push(buf.toString('utf8', offset, offset + len)); offset += len;
}
return { id, timestamps, values, tags };
}
);
// ============================================
// 사용 예시
// ============================================
// Order 쓰기
const writer = new DataWriter<Order>('./data/orders', {
serializer: orderSerializer,
});
writer.open();
writer.append({
orderId: 1001,
items: [
{ sku: 'ITEM-A', qty: 2, price: 10.5 },
{ sku: 'ITEM-B', qty: 1, price: 25.0 },
{ sku: 'ITEM-C-LONG-SKU', qty: 5, price: 5.0 },
],
total: 71.0,
});
writer.append({
orderId: 1002,
items: [{ sku: 'X', qty: 100, price: 1.0 }],
total: 100.0,
});
writer.close();
// Order 읽기
const reader = new DataReader<Order>('./data/orders', orderSerializer);
reader.open();
const orders = reader.getBulkData(1, 2);
console.log(orders);
// [
// { sequence: 1, data: { orderId: 1001, items: [...], total: 71 } },
// { sequence: 2, data: { orderId: 1002, items: [...], total: 100 } },
// ]
reader.close();
```
---
## 가변 배열 직렬화 패턴
```
┌─────────────────────────────────────────────────┐
│ [길이 4bytes] [요소1] [요소2] ... [요소N] │
└─────────────────────────────────────────────────┘
```
| 타입 | 직렬화 방식 |
|------|-------------|
| `number[]` | `[len:4] [val:8] [val:8] ...` |
| `string[]` | `[len:4] [strLen:4] [str] [strLen:4] [str] ...` |
| `Object[]` | `[len:4] [obj1 필드들] [obj2 필드들] ...` |
| `bigint[]` | `[len:4] [val:8] [val:8] ...` |
---
## 헬퍼 함수 (재사용)
```typescript
// dat/binary-helpers.ts
export class BinaryWriter {
private chunks: Buffer[] = [];
writeUInt32(value: number): this {
const buf = Buffer.alloc(4);
buf.writeUInt32LE(value, 0);
this.chunks.push(buf);
return this;
}
writeDouble(value: number): this {
const buf = Buffer.alloc(8);
buf.writeDoubleLE(value, 0);
this.chunks.push(buf);
return this;
}
writeBigUInt64(value: bigint): this {
const buf = Buffer.alloc(8);
buf.writeBigUInt64LE(value, 0);
this.chunks.push(buf);
return this;
}
writeString(value: string): this {
const strBuf = Buffer.from(value, 'utf8');
this.writeUInt32(strBuf.length);
this.chunks.push(strBuf);
return this;
}
writeNumberArray(values: number[]): this {
this.writeUInt32(values.length);
for (const v of values) this.writeDouble(v);
return this;
}
writeStringArray(values: string[]): this {
this.writeUInt32(values.length);
for (const v of values) this.writeString(v);
return this;
}
toBuffer(): Buffer {
return Buffer.concat(this.chunks);
}
}
export class BinaryReader {
private offset = 0;
constructor(private buf: Buffer) {}
readUInt32(): number {
const v = this.buf.readUInt32LE(this.offset);
this.offset += 4;
return v;
}
readDouble(): number {
const v = this.buf.readDoubleLE(this.offset);
this.offset += 8;
return v;
}
readBigUInt64(): bigint {
const v = this.buf.readBigUInt64LE(this.offset);
this.offset += 8;
return v;
}
readString(): string {
const len = this.readUInt32();
const v = this.buf.toString('utf8', this.offset, this.offset + len);
this.offset += len;
return v;
}
readNumberArray(): number[] {
const len = this.readUInt32();
const arr: number[] = [];
for (let i = 0; i < len; i++) arr.push(this.readDouble());
return arr;
}
readStringArray(): string[] {
const len = this.readUInt32();
const arr: string[] = [];
for (let i = 0; i < len; i++) arr.push(this.readString());
return arr;
}
}
```
---
## 헬퍼 사용 예시
```typescript
import { createSerializer } from './src/data-file/index.js';
import { BinaryWriter, BinaryReader } from './src/data-file/binary-helpers.js';
interface Order {
orderId: number;
items: { sku: string; qty: number; price: number }[];
total: number;
}
const orderSerializer = createSerializer<Order>(
(data) => {
const w = new BinaryWriter();
w.writeUInt32(data.orderId);
w.writeUInt32(data.items.length);
for (const item of data.items) {
w.writeString(item.sku);
w.writeUInt32(item.qty);
w.writeDouble(item.price);
}
w.writeDouble(data.total);
return w.toBuffer();
},
(buf) => {
const r = new BinaryReader(buf);
const orderId = r.readUInt32();
const itemsLen = r.readUInt32();
const items = [];
for (let i = 0; i < itemsLen; i++) {
items.push({
sku: r.readString(),
qty: r.readUInt32(),
price: r.readDouble(),
});
}
const total = r.readDouble();
return { orderId, items, total };
}
);
```