# แนะนำ Pulsora: ฐานข้อมูลไทม์ซีรีส์ที่เขียนด้วย Rust สำหรับข้อมูลเทรด

> เราสร้าง Pulsora ฐานข้อมูลไทม์ซีรีส์โอเพนซอร์สที่เขียนด้วย Rust เพราะกระแสข้อมูล tick ของตลาดดันสตอเรจของเราจนเกินขีดจำกัด บล็อกแบบคอลัมน์ การบีบอัดเฉพาะตามชนิดข้อมูล และดัชนีระดับบล็อกที่ไม่โตตามจำนวนแถว

# แนะนำ Pulsora: ฐานข้อมูลไทม์ซีรีส์ที่เขียนด้วย Rust สำหรับข้อมูลเทรด

เรามีกระแสข้อมูลพุ่งเข้ามาแต่ไม่มีที่ดี ๆ ให้เก็บมัน

ปัญหาภายในนั้นเฉพาะเจาะจง: จัดเก็บและคิวรีกระแสข้อมูลตลาดที่ใหญ่และต่อเนื่อง — tick, quote, bar ของสัญลักษณ์จำนวนมาก — ให้เร็วพอที่การนำเข้าจะไม่กลายเป็นคอขวด และคิวรีตามช่วงเวลาคืนค่ากลับมาโดยที่เราไม่ต้องกลั้นหายใจ ข้อมูลตลาดคือเวอร์ชันที่สะอาดและโหดที่สุดของภาระงานไทม์ซีรีส์ มันมาตามลำดับ มันมาตลอดเวลา และมันไม่เคยหยุด ปริมาณไม่ได้น่าสนใจเพราะมันใหญ่ครั้งเดียว แต่มันน่าสนใจเพราะมันใหญ่ทุก ๆ วินาที ตลอดไป

ตอนแรกเราลองสิ่งที่ชัดเจนก่อน: หยิบฐานข้อมูลไทม์ซีรีส์ที่มีอยู่แล้วมาใช้ ปกติแล้วนั่นคือการตัดสินใจที่ถูกต้อง และช่วงหนึ่งมันก็ใช้ได้ดี จากนั้นความต้องการด้านอัตราการเขียนก็ค่อย ๆ สูงขึ้น พื้นที่บนดิสก์โตเร็วยิ่งกว่า และคิวรีตามช่วงเวลาที่เราใช้งานจริงเริ่มรู้สึกถึงน้ำหนักของดัชนี ถึงจุดหนึ่ง ชั้นสตอเรจทำงานบัญชีต่อแถวมากกว่างานจริงบนแถวเสียอีก

เราจึงสร้าง **Pulsora** — ฐานข้อมูลไทม์ซีรีส์ที่เขียนด้วย Rust ปรับให้เหมาะกับข้อมูลตลาดและชุดข้อมูลที่เรียงตามเวลา มันเป็น[โอเพนซอร์สบน GitHub](https://github.com/muvon/pulsora) ภายใต้สัญญาอนุญาต Apache-2.0 โพสต์นี้คือการแนะนำว่ามันคืออะไร ทำไมมันถึงมีอยู่ และจริง ๆ แล้วมันจัดเก็บและคิวรีข้อมูลอย่างไร — อ้างอิงจากโค้ด ไม่ใช่จากโบรชัวร์

---

## ทำไมต้องสร้างเองแทนที่จะรับมาใช้

การสร้างฐานข้อมูลไม่ใช่สิ่งที่ทำกันเล่น ๆ เกณฑ์ที่ต้องผ่านให้ได้คือ: เราต้องการอะไรเป็นพิเศษที่เราไม่ได้รับมาในราคาที่ถูกพอ?

มีสามสิ่งที่ผุดขึ้นมาซ้ำ ๆ

**อัตราการเขียนที่ขยายตามบล็อก ไม่ใช่ตามแถว** กระแส tick คือแถวเล็ก ๆ ที่แทบเหมือนกันนับล้านแถว สตอเรจทั่วไปส่วนใหญ่อยากทำดัชนีให้ทุกแถว นั่นคือรายการดัชนีหนึ่งรายการต่อหนึ่งแถว และที่จำนวนแถวของเรา ดัชนีกลายเป็นต้นทุนหลัก — ทั้งในการขยายการเขียน บนดิสก์ และในการ compaction เราต้องการให้ต้นทุนสตอเรจต่อแถวเข้าใกล้ศูนย์ และให้ดัชนีขยายตามจำนวน _บล็อก_ ที่เราเขียน ไม่ใช่ตามจำนวนแถว

**การบีบอัดที่เข้าใจข้อมูล** ข้อมูลตลาดบีบอัดได้สวยงามหากคุณมองมันเป็นคอลัมน์ของชนิดข้อมูลที่รู้แน่ชัด timestamp มาที่ช่วงเวลาเกือบสม่ำเสมอ ราคาเปลี่ยนช้า ๆ ปริมาณเป็นจำนวนเต็มเล็ก ๆ การบีบอัดแบบเรียงตามแถวทั่วไปทิ้งสิ่งเหล่านั้นไปเกือบหมด เราต้องการการบีบอัดแบบคอลัมน์ที่รู้ชนิดข้อมูล — แบบที่เปเปอร์ Gorilla ของ Facebook วางไว้ — สร้างมาในตัว ไม่ใช่แปะเพิ่มทีหลัง

**อินเทอร์เฟซที่น่าเบื่อและยืดหยุ่นเรื่องฟอร์แมต** เรานำเข้าจากผู้ผลิตข้อมูลหลายแหล่งและบริโภคเข้าสู่เครื่องมือหลายตัว เราไม่ต้องการโปรโตคอลสายเฉพาะตัว เราต้องการ POST CSV จากสคริปต์ สตรีม Apache Arrow จากไปป์ไลน์ และอ่านกลับเป็น JSON, Arrow หรือ CSV ขึ้นอยู่กับว่าใครเป็นคนถาม HTTP และฟอร์แมตคอลัมน์ที่รู้จักกันดี ไม่มีอะไรแปลกประหลาด

ไม่มีข้อใดที่แปลกใหม่ในตัวมันเอง แต่การผสมผสาน — สำหรับภาระงาน _ของเรา_ ด้วยข้อจำกัดด้านพื้นที่ _ของเรา_ — เพียงพอที่จะให้เหตุผลในการเขียนมันขึ้นมา และเมื่อคุณกำลังสร้างมันอยู่แล้ว การเขียนด้วย Rust เพื่อให้ได้ความถูกต้องตอน compile บนเส้นทางร้อนคือส่วนที่ง่าย เราเคยเขียนไว้ก่อนหน้านี้ว่า[ทำไมเราถึงสร้างเครื่องมือของเราเอง](/blog/we-are-builders) Pulsora อยู่ในสายเลือดเดียวกันนั้นพอดี

---

## วันนี้ Pulsora คืออะไร

ขอพูดตรง ๆ เรื่องขอบเขต: Pulsora คือ `0.1.0` มันคือเอนจินที่ทำงานของมันได้ดี ไม่ใช่แพลตฟอร์มแบบคลัสเตอร์ มี replication รองรับหลายผู้เช่าพร้อม query planner มันคือสตอเรจไทม์ซีรีส์แบบคอลัมน์โหนดเดียว ฝัง RocksDB พร้อม REST API เท่านั้นเอง และเท่านั้นเองคือประเด็น

นี่คือรูปร่างของมัน:

- **แบ็กเอนด์สตอเรจ:** RocksDB พร้อมชั้นคอลัมน์ที่เขียนเองทับไว้ด้านบน
- **ฟอร์แมตการนำเข้า:** CSV, Apache Arrow IPC stream และ Protocol Buffers
- **ฟอร์แมตผลลัพธ์ของคิวรี:** JSON (ค่าเริ่มต้น), Arrow IPC stream, Protobuf และ CSV — เจรจาผ่านเฮดเดอร์ `Accept`
- **สคีมา:** อนุมานอัตโนมัติตอนนำเข้าครั้งแรก คุณไม่ต้องประกาศตาราง คุณ POST ข้อมูลแล้ว Pulsora หาคอลัมน์และชนิดข้อมูลเอง
- **อินเทอร์เฟซ:** HTTP/REST API ที่ให้บริการโดย Axum บน Tokio
- **ความทนทาน:** write-ahead log แบบเลือกเปิดได้ เพื่อให้แถวที่อยู่ในบัฟเฟอร์รอดจากการแครช

ทั้งหมดคือไบนารีเดียวชื่อ `pulsora` มีแฟล็กบรรทัดคำสั่งเดียว

```bash
# รันด้วยค่าเริ่มต้น
cargo run

# หรือชี้ไปยังไฟล์คอนฟิก
cargo run -- -c pulsora.toml
```

แฟล็ก `-c/--config` นั้นคือพื้นผิวบรรทัดคำสั่งทั้งหมด ที่เหลือทั้งหมดอยู่ใน TOML นี่เป็นความตั้งใจ — คอนฟิกคือ API สำหรับการดำเนินงาน ส่วน API ตอนรันไทม์คือ HTTP

---

## มันจัดเก็บข้อมูลบนดิสก์อย่างไร

นี่คือส่วนที่เราใส่ใจมากที่สุด จึงเป็นส่วนที่ควรค่าแก่การอธิบาย

### บล็อกแบบคอลัมน์ ไม่ใช่แถว

แถวเข้ามา แต่มันไม่ได้ถูกจัดเก็บเป็นแถว Pulsora สะสมแถวที่เข้ามาในบัฟเฟอร์ในหน่วยความจำ และเมื่อบัฟเฟอร์ถึงเกณฑ์ขนาด (`buffer_size` ค่าเริ่มต้น 1000) หรือเกณฑ์เวลา (`flush_interval_ms`) มันจะจัดกลุ่มแถวเหล่านั้นเป็น **ColumnBlock** — ก้อนของแถวที่จัดเก็บแบบคอลัมน์ต่อคอลัมน์ แต่ละคอลัมน์ถูกบีบอัดอย่างอิสระด้วยอัลกอริทึมที่เลือกตามชนิดข้อมูลของมัน

บล็อกคือหน่วยของการจัดเก็บ `ColumnBlock` มีหน้าตาคร่าว ๆ ดังนี้:

```rust
pub struct ColumnBlock {
    pub row_count: usize,                        // จำนวนแถวในบล็อกนี้
    pub columns: HashMap<String, Vec<u8>>,       // ข้อมูลคอลัมน์ที่บีบอัดแล้ว
    pub null_bitmaps: HashMap<String, Vec<u8>>,  // การติดตามค่า null ต่อคอลัมน์
}
```

การจัดเก็บแบบคอลัมน์แทนที่จะเป็นแถวคือสิ่งที่ทำให้การบีบอัดได้ผล เพราะทุกค่าในคอลัมน์มีชนิดข้อมูลเดียวกันและขนาดใกล้เคียงกัน นั่นคือเหตุผลทั้งหมดที่สตอเรจแบบคอลัมน์มีอยู่ และข้อมูลไทม์ซีรีส์คือภาระงานที่มันถูกคิดค้นขึ้นมาเพื่อรองรับ

### การบีบอัดเฉพาะตามชนิดข้อมูล

คอลัมน์แต่ละชนิดได้รับกลยุทธ์การบีบอัดที่สร้างมาเพื่อรูปร่างของมัน ตามเอกสารของ repository:

| ชนิดข้อมูล | การบีบอัด                       | อัตราส่วนทั่วไป (ตัวเลขของ repo) | ทำไมจึงได้ผล              |
| ---------- | ------------------------------- | -------------------------------- | ------------------------- |
| Timestamp  | Delta-of-delta + varint         | 5–10x                            | ช่วงเวลาเกือบสม่ำเสมอ     |
| Float      | XOR (Gorilla) + varfloat        | 2–5x                             | ค่าที่เปลี่ยนแปลงช้า      |
| Integer    | Delta + varint                  | 3–8x                             | ข้อมูลเรียงลำดับ / ตัวนับ |
| String     | การเข้ารหัสแบบพจนานุกรม         | 2–4x                             | ค่าที่ซ้ำ ๆ (สัญลักษณ์)   |
| Boolean    | การเข้ารหัสแบบ run-length (RLE) | 10–50x                           | ข้อมูลกระจัดกระจาย        |

เส้นทางของ timestamp คือกรณีตำรา tick มาที่ช่วงเวลาที่ _เกือบ_ คงที่ ดังนั้นผลต่างอันดับสอง (delta-of-delta) มักเป็นศูนย์หรือเล็กจิ๋ว และ varint เข้ารหัสตัวเลขจิ๋ว ๆ ในหนึ่งไบต์ Float ใช้การแพ็กบิตแบบ XOR-กับค่าก่อนหน้า จากอัลกอริทึม Gorilla — เมื่อราคาแทบไม่ขยับ บิตที่ผ่าน XOR ส่วนใหญ่เป็นศูนย์และถูกแพ็กจนแทบหายไป สตริงอย่างสัญลักษณ์ ticker ซ้ำไม่รู้จบ ดังนั้นพจนานุกรมจึงแมปแต่ละสัญลักษณ์ที่ต่างกันไปยังจำนวนเต็มเล็ก ๆ เพียงครั้งเดียว

ใต้ทั้งหมดนั้น RocksDB ใส่การบีบอัดบล็อกทั่วไปอีกชั้นหนึ่ง — `lz4` เป็นค่าเริ่มต้น โดยมี `none`, `snappy` และ `zstd` ให้เลือก ดังนั้นคุณจึงได้การเข้ารหัสที่รู้ชนิดข้อมูลก่อน แล้วตามด้วยการบีบอัดทั่วไปทับด้านบน

อัตราส่วนเหล่านี้คือคำกล่าวอ้างเรื่องเบนช์มาร์กของ repository เอง วัดด้วยชุด Criterion ภายใต้ `benches/` ผมรายงานมันในฐานะตัวเลขของโปรเจกต์ ไม่ใช่คำตัดสินอิสระ — ลองรัน `cargo bench` กับข้อมูลของคุณเองและเชื่อตัวเลขนั้นแทน

### ดัชนีที่ไม่โตตามจำนวนแถว

นี่คือการตัดสินใจด้านดีไซน์ที่ทุกอย่างหมุนรอบมัน

สตอเรจไทม์ซีรีส์ส่วนใหญ่เก็บดัชนีต่อแถวเพื่อให้หาแถวเดี่ยวใด ๆ ตามเวลาได้ Pulsora ไม่ทำ มันเก็บ **ดัชนีระดับบล็อกเท่านั้น** — เมตาดาตาเกี่ยวกับแต่ละบล็อก ไม่เคยเกี่ยวกับแถวรายตัว เลย์เอาต์คีย์บนดิสก์ ตามเอกสารสถาปัตยกรรม มีหน้าตาดังนี้:

```
Block index: [table_hash:u32]['B'][min_ts:i64][block_id:u64]
             value: [block_id][min_ts][max_ts][rows][min_id][max_id]   — หนึ่งรายการต่อบล็อก
Block data:  [table_hash:u32]['D'][block_id:u64]                       — บล็อกแบบคอลัมน์ที่บีบอัดแล้ว
Overrides:   [table_hash:u32]['O'][block_id:u64]                       — ตำแหน่งของแถวที่ตายแล้ว
```

`table_hash` คือแฮช FNV-1a ขนาด 4 ไบต์ของชื่อตาราง ใช้เป็นคีย์พรีฟิกซ์เพื่อให้ตารางถูกแยกกันโดยไม่ต้องลากชื่อสตริงไปกับทุกคีย์ หลังพรีฟิกซ์ ไบต์เดียว (`'B'`, `'D'`, `'O'`) แยกดัชนี ข้อมูล และชุด override ออกจากกัน

ผลที่ตามมา: ต้นทุนสตอเรจขยายตามจำนวนบล็อก ไม่ใช่จำนวนแถว เขียนหนึ่งพันล้านแถวแล้วคุณไม่ได้เพิ่มรายการดัชนีใด ๆ นอกเหนือจากบล็อกที่ใส่มันไว้ นั่นคือข้อกำหนดที่เราซื้อในราคาถูกไม่ได้ และคือแก่นของเหตุผลที่ Pulsora มีอยู่

การอัปเดตแถวจัดการได้โดยไม่ต้องเขียนบล็อกใหม่ REPLACE จะทำเครื่องหมายตำแหน่งของสำเนาเดิมที่ถูกแทนที่ใน **ชุด override** ของบล็อกเก่า — ชุดของตำแหน่งแถวที่ตายแล้วต่อบล็อก ถูกผสานผ่านตัวดำเนินการ merge ของ RocksDB (set union) ใน write batch เดียวกันกับข้อมูลใหม่ การตรวจว่าแถวยังมีชีวิตคือการอ่านแบบจุดหนึ่งครั้งต่อบล็อก ไม่ใช่การ lookup ต่อแถว

---

## เส้นทางการเขียน

การนำเข้าคือ POST สคีมาถูกอนุมานในครั้งแรก

```bash
curl -X POST http://localhost:8080/tables/stocks/ingest \
  -H "Content-Type: text/csv" \
  --data-binary @ticks.csv
```

แถวเฮดเดอร์ของ CSV นั้นกลายเป็นสคีมา Pulsora สุ่มตัวอย่างค่า ตรวจหาชนิดของแต่ละคอลัมน์ (Integer, Float, String, Boolean, Timestamp) และระบุคอลัมน์ timestamp อัตโนมัติ — มันเข้าใจ RFC 3339, `YYYY-MM-DD HH:MM:SS`, แบบวันที่อย่างเดียว และ Unix timestamp ทั้งแบบวินาทีและมิลลิวินาที จากนั้นเป็นต้นมา สคีมาของตารางนั้นถูกตรึงไว้และข้อมูลที่เข้ามาจะถูกตรวจสอบกับมัน

สำหรับไปป์ไลน์ที่ต้องการอัตราสูงกว่า ข้ามการแพร์ส CSV ไปเลยแล้วสตรีม Apache Arrow:

```bash
curl -X POST http://localhost:8080/tables/stocks/ingest \
  -H "Content-Type: application/vnd.apache.arrow.stream" \
  --data-binary @ticks.arrow
```

Arrow มาพร้อมสคีมาที่แนบมาแล้ว จึงไม่มีขั้นตอนอนุมานและไม่มีการแพร์สข้อความ — คอลัมน์เป็นคอลัมน์อยู่แล้ว การนำเข้า Protobuf ทำงานแบบเดียวกันสำหรับผู้ผลิตข้อมูลที่พูดมันได้อยู่แล้ว

ภายในนั้น ขั้นตอนคือ: แพร์สอินพุต ตรวจสอบหรืออนุมานสคีมา บัฟเฟอร์แถวในหน่วยความจำ (ขจัดข้อมูลซ้ำตอนเข้ามา การเขียนล่าสุดชนะ) และ — หากเปิด WAL — ต่อท้ายแต่ละแถวลงในไฟล์ `.wal` ก่อนที่มันจะแตะหน่วยความจำ เพื่อให้การแครชไม่ทำให้ข้อมูลในบัฟเฟอร์หาย เมื่อบัฟเฟอร์ flush แถวจะกลายเป็น ColumnBlock ที่บีบอัด บล็อกและรายการดัชนีของมันถูกเขียนลง RocksDB ใน batch เดียว และ WAL ถูกตัดทิ้ง (truncate)

---

## เส้นทางการอ่าน

คิวรีคือ GET พร้อมช่วงเวลาและการแบ่งหน้า

```bash
# ทั้งหมด (ถูกจำกัดด้วย limit ค่าเริ่มต้น)
curl "http://localhost:8080/tables/stocks/query"

# ช่วงเวลาหนึ่ง
curl "http://localhost:8080/tables/stocks/query?start=2024-01-01T09:30:00&end=2024-01-01T16:00:00"

# แบ่งหน้า
curl "http://localhost:8080/tables/stocks/query?limit=1000&offset=0"
```

`start` และ `end` รับฟอร์แมต timestamp ชุดที่ยืดหยุ่นชุดเดียวกับการนำเข้า `limit` ค่าเริ่มต้นคือ 1000 โดยไม่มีการบังคับค่าสูงสุด ส่วน `offset` ข้ามแถวเพื่อการแบ่งหน้า

เอนจินคิวรีแก้คำขอด้วยดัชนีบล็อกก่อน: มันสร้างช่วงคีย์ไบนารีจากขอบเขตเวลาและวนเฉพาะบล็อกที่ `[min_ts, max_ts]` ทับซ้อนกับช่วงนั้น มันรวบรวมบล็อกผู้สมัคร ดึงแต่ละบล็อกที่ไม่ซ้ำมาครั้งเดียว คลายการบีบอัด แคชผลลัพธ์ที่คลายแล้วเพื่อให้การเข้าถึงซ้ำภายในคิวรีไม่ต้องคลายใหม่ ใช้ชุด override เพื่อข้ามแถวที่ตายแล้ว ดึงแถวที่ต้องการ และซีเรียลไลซ์มันในฟอร์แมตที่เฮดเดอร์ `Accept` ขอ

อยากได้ผลลัพธ์กลับมาเป็น Arrow สำหรับไปป์ไลน์ปลายทางแทน JSON? เปลี่ยนเฮดเดอร์เดียว:

```bash
curl "http://localhost:8080/tables/stocks/query?limit=10000" \
  -H "Accept: application/vnd.apache.arrow.stream" > out.arrow

curl "http://localhost:8080/tables/stocks/query?limit=10000" \
  -H "Accept: text/csv" > out.csv
```

คิวรีเดียวกัน สี่ฟอร์แมตผลลัพธ์ที่เป็นไปได้ ไม่มีขั้นตอนแปลงฝั่งคุณ ยังมีเอนด์พอยต์อ่านอีกหยิบมือหนึ่ง — `GET /tables` เพื่อแสดงรายการตาราง, `GET /tables/{name}/schema` สำหรับสคีมาที่อนุมาน, `GET /tables/{name}/count` สำหรับนับจำนวนแถว, `GET /tables/{name}/row/{id}` เพื่อดึงแถวเดียวด้วย id และ `GET /health`

---

## ลองใช้

Pulsora build ได้ด้วย Rust toolchain ปัจจุบันและรันเป็นไบนารีเดียว

```bash
# โคลนและรันจากซอร์ส
git clone https://github.com/muvon/pulsora.git
cd pulsora
cargo run

# หรือติดตั้งไบนารีโดยตรง
cargo install --git https://github.com/muvon/pulsora.git

# นำเข้า CSV
curl -X POST http://localhost:8080/tables/stocks/ingest \
  -H "Content-Type: text/csv" \
  -d "timestamp,symbol,price,volume
2024-01-01 09:30:00,AAPL,150.00,1000
2024-01-01 09:31:00,AAPL,150.25,1500
2024-01-01 09:32:00,AAPL,149.75,2000"

# อ่านกลับ
curl "http://localhost:8080/tables/stocks/query?limit=10"

# ดูสคีมาที่อนุมาน
curl "http://localhost:8080/tables/stocks/schema"
```

การปรับจูนอยู่ใน `pulsora.toml` การตั้งค่าที่ควรรู้ก่อน:

```toml
[storage]
data_dir = "./data"
buffer_size = 1000          # แถวที่บัฟเฟอร์ก่อนเขียนบล็อก
flush_interval_ms = 1000    # เวลาสูงสุดที่แถวรอในบัฟเฟอร์
wal_enabled = true          # แถวในบัฟเฟอร์รอดจากการแครช

[ingestion]
batch_size = 10000          # แถวต่อ batch การเขียนลงฐานข้อมูล

[performance]
compression = "lz4"         # none | snappy | lz4 | zstd
cache_size_mb = 256         # แคชบล็อกสำหรับการอ่าน
```

หากคุณให้ความสำคัญกับการบีบอัดสูงสุดมากกว่าความหน่วง ตั้ง `flush_interval_ms = 0` เพื่อโหมด batch-only ที่กั้นแถวไว้จนกว่าบัฟเฟอร์จะเต็ม ทำให้ได้บล็อกที่ใหญ่กว่าและบีบอัดดีกว่า หากคุณให้ความสำคัญกับความหน่วงในการอ่านข้อมูลร้อน เพิ่ม `cache_size_mb` ชุดปุ่มปรับทั้งหมดมีบันทึกไว้ใน `doc/CONFIGURATION.md`

---

## มันไม่ใช่อะไร และต่อไปคืออะไร

ผมจะช่วยคุณให้ไม่ต้องผิดหวังจากการค้นพบสิ่งเหล่านี้ด้วยวิธีที่ยากลำบาก วันนี้ Pulsora เป็นโหนดเดียว ไม่มีคลัสเตอร์ ไม่มี replication ไม่มี SQL ไม่มี query planner ทั่วไป ไม่มี predicate pushdown นอกเหนือจากช่วงเวลา ไม่มีการจำกัดอัตรา คิวรีกรองตามเวลาและแบ่งหน้า มันไม่ทำ `GROUP BY` ไฟล์ `doc/ARCHITECTURE.md` ระบุทิศทางที่เรากำลังมองอยู่ — column pruning และ predicate pushdown, แคชบล็อกที่หนุนหลังด้วยดิสก์, สตอเรจแบบแบ่งชั้นร้อน/อุ่น/เย็น และการทำคลัสเตอร์ — แต่นั่นคือโรดแมป ไม่ใช่ความจริง ผมอยากให้คุณรู้ขอบเขตมากกว่าให้คุณไปชนมัน

สิ่งที่มัน _เป็น_ ในวันนี้คือเอนจินที่โฟกัส ซึ่งนำเข้ากระแสข้อมูลที่เรียงตามเวลา บีบอัดมันอย่างหนักด้วยอัลกอริทึมที่เข้าใจมัน และให้บริการคิวรีตามช่วงเวลาโดยไม่มีดัชนีที่พองตามจำนวนแถว นั่นคือปัญหาที่เรามี และเป็นปัญหาที่ Pulsora แก้

---

## โอเพนซอร์ส

Pulsora อยู่[บน GitHub](https://github.com/muvon/pulsora) ภายใต้ Apache-2.0 มันคือ Rust — RocksDB สำหรับสตอเรจ, Axum และ Tokio สำหรับ API, Arrow และ Prost สำหรับฟอร์แมตคอลัมน์และ protobuf — และ build ได้ด้วย `cargo build` ธรรมดา ชุดเบนช์มาร์กอยู่ใน `benches/` หากคุณอยากวัดประสิทธิภาพการนำเข้าและคิวรีกับข้อมูลของคุณเองแทนที่จะเชื่อของเรา

หากคุณกำลังจมอยู่ในแถวที่เรียงตามเวลา — tick ตลาด ค่าจากเซ็นเซอร์ อะไรก็ตามที่มาตามลำดับและไม่เคยหยุด — โคลนมัน ชี้สตรีมไปที่มัน แล้วบอกเราว่ามันพังตรงไหน นั่นคือทางที่เร็วที่สุดที่มันจะดีขึ้น

— Don

_Pulsora เป็นโอเพนซอร์สบน GitHub ที่ [github.com/muvon/pulsora](https://github.com/muvon/pulsora) ภายใต้สัญญาอนุญาต Apache-2.0 เจอบั๊กหรืออยากได้ฟีเจอร์? [เปิด issue](https://github.com/muvon/pulsora/issues)_
