Telemetry Lifecycle
Bu sayfa, Cınga backend içinde bir telemetry paketinin veritabanı açısından nasıl yaşadığını tanımlar. Amaç yalnız tablo listesi vermek değil; her tablonun neden var olduğunu, kabul zincirindeki yerini, hangi veriyi tuttuğunu, hangi veriyi bilerek tutmadığını ve hangi alanların ne amaçla eklendiğini netleştirmektir.
Bu doküman koda başlamadan önce veri sözleşmesini sabitlemek içindir. Bu nedenle burada yazılanlar sadece açıklama değil, davranış kontratıdır.
Genel Akış
Bu akışta temel roller şunlardır:
raw_data: ingress journalstreams: accepted telemetry eventmeasurement.*: enerji odaklı typed measurement persistencemeasurements: generic düşük yoğunluklu measurement persistencedevice_runtime_state: son canlı çalışma özetidevice_register_state: son register snapshotledger_transactions: best-effort integrity trail
1. raw_data
raw_data, sisteme gelen paketin ilk ve en ham journal kaydıdır. Bu tablonun görevi accepted veri üretmek değil, giriş izini kaybetmemektir.
Neden vardır?
- cihazdan tam olarak ne geldiğini göstermek
- bozuk JSON veya schema fail payload’ları saklamak
- duplicate paketleri görünür kılmak
- firmware ekibine ve operasyon ekibine hata inceleme zemini sunmak
- accepted olmamış paketler için de tarihsel kayıt tutmak
Neden ayrı tablodur?
Çünkü accepted olmayan paketler de görünür olmalıdır. streams yalnız accepted olayları tutar; raw_data ise acceptance öncesi giriş jurnali görevi görür.
Ne zaman kayıt oluşur?
Bir paket sisteme ulaştığında en erken kalıcı kayıt burada açılır. Parse veya schema sonucu ne olursa olsun ingress izi bu tabloda görünmelidir.
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
id | int | hayır | Raw journal kaydının birincil anahtarı |
stream_id | int | evet | Bu raw kayıttan accepted stream üretildiyse ilgili stream ilişkisi |
device_time | timestamp | evet | Payload içinden çözümlenebilen cihaz zamanı |
device_id | varchar(21) | evet | Payload içinden çözümlenebilen cihaz kimliği |
client_ip | varchar(45) | evet | Paketin geldiği IP bilgisi |
raw_pack | text | evet | Cihazdan geldiği haliyle wire payload |
pack_size | int | evet | HTTP body boyutu |
valid_pack | boolean | hayır | Parse + schema açısından accepted aday olup olmadığı |
validation_note | varchar(255) | evet | Hata veya açıklama notu |
stream_time | timestamp | hayır | Sistemin paketi aldığı/işlediği zaman |
Önerilen ek kolonlar
Bu tablo için aşağıdaki alanlar eklenmesi önerilir:
| Kolon | Tip | Neden gerekli? |
|---|---|---|
ingest_status | varchar(30) / enum | Raw kaydın neden accepted veya rejected olduğunu tek bakışta görmek için |
failure_stage | varchar(30) / enum | Hatanın parse, schema, duplicate veya measurement aşamasında mı olduğunu ayırmak için |
Önerilen ingest_status değerleri
| Değer | Anlamı |
|---|---|
received | Journal kaydı oluştu, işleme başladı |
invalid_json | JSON parse edilemedi |
schema_failed | JSON parse edildi ama CPS geçmedi |
duplicate | Duplicate identity nedeniyle accepted zincire alınmadı |
accepted | Accepted zincire girdi ve stream oluştu |
failed | Accepted zincire girdi ama sonraki aşamalardan birinde başarısız oldu |
Örnek kayıtlar
Accepted raw örneği:
{
"id": 101,
"stream_id": 5001,
"device_time": "2026-04-03T10:29:50Z",
"device_id": "46000000C47CA670",
"client_ip": "10.10.1.25",
"raw_pack": "{\"command\":\"timed\",\"device_id\":\"46000000C47CA670\",\"Payload\":{...}}",
"pack_size": 512,
"valid_pack": true,
"validation_note": null,
"ingest_status": "accepted",
"failure_stage": null,
"stream_time": "2026-04-03T10:30:00Z"
}
Invalid JSON örneği:
{
"id": 102,
"stream_id": null,
"device_time": null,
"device_id": null,
"client_ip": "10.10.1.99",
"raw_pack": "{\"command\":\"timed\",\"Payload\":{\"VRMS\":[0,0,0]",
"pack_size": 128,
"valid_pack": false,
"validation_note": "JSON parse hatası",
"ingest_status": "invalid_json",
"failure_stage": "parse",
"stream_time": "2026-04-03T10:31:00Z"
}
Duplicate örneği:
{
"id": 103,
"stream_id": null,
"device_time": "2026-04-03T10:29:50Z",
"device_id": "46000000C47CA670",
"client_ip": "10.10.1.25",
"raw_pack": "{\"command\":\"timed\",\"device_id\":\"46000000C47CA670\",\"Payload\":{...}}",
"pack_size": 512,
"valid_pack": true,
"validation_note": "device_id + device_time + canonical hash eşleşti",
"ingest_status": "duplicate",
"failure_stage": "duplicate",
"stream_time": "2026-04-03T10:30:03Z"
}
Ne içermez?
- canonical measurement kolonları
- runtime state özeti
- ledger sonucu
- rule engine state’i
2. streams
streams, accepted telemetry olayının resmi tablosudur. Bu tabloda yalnız valid, accepted ve non-duplicate telemetry event’ler yer alır.
Neden vardır?
Çünkü downstream servislerin raw_data içindeki her journal kaydını yorumlaması gerekmez. Onlar yalnız accepted olaylarla ilgilenir.
Ne zaman kayıt oluşur?
Şu koşullar birlikte sağlandıktan sonra oluşur:
- raw journal kaydı var
- JSON parse başarılı
- CPS schema validation başarılı
- paket duplicate değil
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
id | int | hayır | Accepted stream birincil anahtarı |
device_id | varchar(21) | hayır | Accepted paketin cihazı |
message_type_id | int | evet | Paket tipi (timed, interrupt, alarm vb.) |
sim_id | int | evet | Paket o anda hangi SIM üzerinden geldiyse ilişki |
ip_address | varchar(45) | evet | Kabul edilen isteğin IP bilgisi |
payload_size | int | evet | Kabul edilen body boyutu |
process_time_ms | float | evet | Kabul zincirinin maliyeti |
device_time | timestamp | hayır | Cihazın payload içinde bildirdiği zaman |
stream_time | timestamp | hayır | Sistemin accepted event zamanı |
Önerilen ek kolonlar
| Kolon | Tip | Neden gerekli? |
|---|---|---|
raw_id | int | Stream’in hangi raw kayıttan türediğini net bağlamak için |
ledger_status | varchar(20) / enum | Ledger yazım sonucunu görünür kılmak için |
ledger_status değerleri
| Değer | Anlamı |
|---|---|
pending | Ledger henüz denenmedi |
written | Ledger başarıyla yazıldı |
failed | Ledger yazımı başarısız oldu |
skipped | Bilinçli olarak atlandı |
Örnek kayıt
{
"id": 5001,
"raw_id": 101,
"device_id": "46000000C47CA670",
"message_type_id": 1,
"sim_id": 1,
"ip_address": "10.10.1.25",
"payload_size": 512,
"process_time_ms": 18.4,
"device_time": "2026-04-03T10:29:50Z",
"stream_time": "2026-04-03T10:30:00Z",
"ledger_status": "pending"
}
Ne içermez?
- wire raw payload
- faz/harmonik measurement kolonları
- duplicate paketler
- invalid journal kayıtları
3. measurement.voltage
measurement.voltage, accepted enerji paketlerinin gerilim segmentini typed ve performanslı biçimde saklar.
Neden vardır?
Gerilim verisi faz, harmonik ve temel bileşen gibi çok sayıda ilişkili kolon üretir. Bu veriyi generic satır tablosunda tutmak pahalı ve okunaksız olur.
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
stream_id | int | hayır | Bu gerilim kaydının ait olduğu accepted stream |
device_id | varchar(21) | hayır | Cihaz kimliği |
sequence_no | int | evet | Cihaz sequence alanı varsa |
device_time | timestamp | hayır | Ölçüm zamanı |
sample_period_sec | int | hayır | Örnekleme periyodu |
is_valid | boolean | hayır | Segment seviyesinde geçerlilik |
vrms_r/s/t | float | evet | Faz RMS gerilimleri |
vrms_a | float | evet | Ortalama faz gerilimi |
fq | float | evet | Şebeke frekansı |
vfund_r/s/t | float | evet | Temel gerilim bileşenleri |
vharm_* | float | evet | Harmonik bileşenler |
create_time | timestamp | hayır | Kayıt zamanı |
update_time | timestamp | hayır | Son güncelleme |
Örnek kayıt
{
"stream_id": 5001,
"device_id": "46000000C47CA670",
"sequence_no": 1,
"device_time": "2026-04-03T10:29:50Z",
"sample_period_sec": 900,
"is_valid": true,
"vrms_r": 229.4,
"vrms_s": 228.9,
"vrms_t": 230.1,
"vrms_a": 229.47,
"fq": 50.0,
"vfund_r": 229.1,
"vfund_s": 228.6,
"vfund_t": 229.8,
"vharm_r_3": 0.8,
"vharm_s_3": 0.7,
"vharm_t_3": 0.9
}
4. measurement.current
measurement.current, accepted enerji paketlerinin akım ve tepe akım segmentini tutar.
Neden vardır?
Akım verisi de gerilim gibi faz bazlı, harmonikli ve yoğun alan seti taşır. Ayrıca IPEAK ve IFUND gibi alt bileşenler operasyonel analizde kritik olduğu için ayrı tutulur.
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
stream_id | int | hayır | Accepted stream ilişkisi |
device_id | varchar(21) | hayır | Cihaz kimliği |
sequence_no | int | evet | Sequence değeri |
device_time | timestamp | hayır | Ölçüm zamanı |
sample_period_sec | int | hayır | Örnekleme periyodu |
is_valid | boolean | hayır | Segment validliği |
irms_r/s/t | float | evet | RMS akımları |
irms_a | float | evet | Ortalama RMS akım |
ipeak_r/s/t | float | evet | Faz tepe akımları |
ifund_r/s/t | float | evet | Temel akım bileşenleri |
iharm_* | float | evet | Harmonik akım bileşenleri |
create_time | timestamp | hayır | Kayıt zamanı |
update_time | timestamp | hayır | Son güncelleme |
Örnek kayıt
{
"stream_id": 5001,
"device_id": "46000000C47CA670",
"device_time": "2026-04-03T10:29:50Z",
"irms_r": 42.8,
"irms_s": 42.2,
"irms_t": 43.0,
"irms_a": 42.67,
"ipeak_r": 61.2,
"ipeak_s": 60.8,
"ipeak_t": 61.5,
"ifund_r": 42.3,
"ifund_s": 41.8,
"ifund_t": 42.6,
"is_valid": true
}
5. measurement.power
Bu tablo aktif, reaktif, görünür güç ve güç faktörü odaklı accepted enerji değerlerini taşır.
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
stream_id | int | hayır | Accepted stream |
device_id | varchar(21) | hayır | Cihaz |
sequence_no | int | evet | Sequence |
device_time | timestamp | hayır | Ölçüm zamanı |
sample_period_sec | int | hayır | Örnekleme periyodu |
is_valid | boolean | hayır | Segment validliği |
p_* | float | evet | Aktif güç alanları |
q_* | float | evet | Reaktif güç alanları |
s_* | float | evet | Görünür güç alanları |
pf_* | float | evet | Güç faktörü |
pfund_* | float | evet | Temel güç faktörü |
qfund_* | float | evet | Temel reaktif bileşen |
create_time | timestamp | hayır | Kayıt zamanı |
update_time | timestamp | hayır | Son güncelleme |
6. measurement.energy
Bu tablo enerji sayaçlarını accepted stream bazında saklar.
Neden vardır?
Enerji sayaçları diğer segmentlerden farklı olarak anlık değil, birikimli ve sayaç mantığında değerler üretir. Bu nedenle kendi segment tablosunda yaşaması daha doğrudur.
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
stream_id | int | hayır | Accepted stream |
device_id | varchar(21) | hayır | Cihaz |
sequence_no | int | evet | Sequence |
device_time | timestamp | hayır | Ölçüm zamanı |
sample_period_sec | int | hayır | Örnekleme periyodu |
is_valid | boolean | hayır | Segment validliği |
ae_* | bigint | evet | Aktif enerji sayaçları |
re_ind_* | bigint | evet | Endüktif reaktif sayaçları |
re_cap_* | bigint | evet | Kapasitif reaktif sayaçları |
create_time | timestamp | hayır | Kayıt zamanı |
update_time | timestamp | hayır | Son güncelleme |
7. measurements
measurements generic ve düşük yoğunluklu standart ölçümler için kullanılır. Bu tablo bütün telemetry verisinin ana deposu değildir.
Neden vardır?
Sıcaklık, nem, basınç, bazı register-türevi sayısal alanlar veya ileride enerji-dışı scalar veriler için esnek bir yapı gerekir.
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
id | int | hayır | Ölçüm satırı anahtarı |
stream_id | int | hayır | Accepted stream ilişkisi |
variable_id | varchar(30) | hayır | Hangi değişken olduğu |
value | decimal(18,6) | evet | Ölçüm değeri |
calibrated | boolean | hayır | Kalibrasyon uygulanmış mı |
synthesized | boolean | hayır | Sentez kuralıyla mı üretildi |
create_time | timestamp | hayır | Oluşturma zamanı |
Örnek kayıt
{
"id": 9001,
"stream_id": 5002,
"variable_id": "TEMP",
"value": 24.7,
"calibrated": false,
"synthesized": false,
"create_time": "2026-04-03T10:25:00Z"
}
Açık kural
Enerji odaklı çok kolonlu telemetry alanları measurement.* tablolarında tutulur. measurements generic tablo enerji için ikinci bir storage alanı olarak kullanılmaz.
8. device_runtime_state (önerilen tablo)
Bu tablo, cihazın son canlı çalışma özetini taşır. devices tablosundan ayrılması önerilir.
Önerilen kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
device_id | varchar(21) | hayır | Cihaz kimliği |
last_connection_ip | varchar(45) | evet | Son bağlantı IP’si |
last_connection_time | timestamp | evet | Son bağlantı zamanı |
last_stream_id | int | evet | Son accepted stream |
last_raw_id | int | evet | Son raw journal kaydı |
last_device_time | timestamp | evet | Cihazın son bildirdiği zaman |
last_seen_status | varchar(30) | evet | Son sağlık/erişim özeti |
update_time | timestamp | hayır | Son güncelleme |
Örnek kayıt
{
"device_id": "46000000C47CA670",
"last_connection_ip": "10.10.1.25",
"last_connection_time": "2026-04-03T10:30:00Z",
"last_stream_id": 5001,
"last_raw_id": 101,
"last_device_time": "2026-04-03T10:29:50Z",
"last_seen_status": "online",
"update_time": "2026-04-03T10:30:00Z"
}
9. device_register_state (önerilen tablo)
Bu tablo son register snapshot’ını tutar.
Önerilen kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
device_id | varchar(21) | hayır | Cihaz kimliği |
register_status | bigint | evet | Son durum register’ı |
register_stop | bigint | evet | Son stop register’ı |
register_publish | bigint | evet | Son publish register’ı |
update_time | timestamp | hayır | Son güncelleme |
Örnek kayıt
{
"device_id": "46000000C47CA670",
"register_status": 5,
"register_stop": 0,
"register_publish": 1,
"update_time": "2026-04-03T10:30:00Z"
}
10. ledger_transactions
Accepted stream için best-effort integrity trail tablosudur.
Neden vardır?
- accepted verinin hash-zincirli denetim izini tutmak
- değişmezlik / audit ihtiyacına taban hazırlamak
- cihaz bazlı zincirli kayıt oluşturmak
Kolonlar
| Kolon | Tip | Null | Anlamı |
|---|---|---|---|
id | bigint | hayır | Ledger kaydı anahtarı |
device_id | varchar(21) | hayır | Cihaz |
stream_id | int | hayır | İlgili accepted stream |
device_time | timestamp | hayır | Ölçüm zamanı |
payload | json | hayır | Accepted payload / snapshot |
payload_hash | varchar(64) | hayır | Payload hash’i |
previous_hash | varchar(64) | hayır | Önceki zincir halkası |
transaction_hash | varchar(64) | hayır | Bu halkaya ait hash |
source_signature | text | evet | Kaynak imzası varsa |
create_time | timestamp | hayır | Oluşturma zamanı |
Örnek kayıt
{
"id": 1,
"device_id": "46000000C47CA670",
"stream_id": 5001,
"device_time": "2026-04-03T10:29:50Z",
"payload_hash": "1111111111111111111111111111111111111111111111111111111111111111",
"previous_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"transaction_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"create_time": "2026-04-03T10:30:01Z"
}
Ledger fail olursa
Bu modelde ledger yazımı kabul zincirini geri almaz.
Sonuç:
- accepted stream kalır
- measurement kalır
- runtime state kalır
- ledger eksik kalabilir
streams.ledger_status = failedolur
Duplicate Identity Spesifikasyonu
Duplicate kararı şu üçlüden oluşur:
device_iddevice_timecanonical_payload_hash
Bu üçü aynıysa paket duplicate kabul edilir.
Duplicate olduğunda
raw_datakaydı tutuluringest_status = duplicate- stream oluşmaz
- measurement yazılmaz
- runtime state güncellenmez
- warning/log üretilebilir
Transaction Boundary
Boundary 1 — Journal
raw_data journal kaydı erken ve bağımsız yazılır.
Boundary 2 — Accepted transaction
Aşağıdakiler aynı transaction içinde değerlendirilir:
streamsinsertmeasurement.*insertlerimeasurementsinsertleridevice_runtime_stateupdatedevice_register_stateupdateraw_data.ingest_status = accepted
Bu zincirin herhangi bir noktasında hata olursa accepted transaction rollback olur.
Boundary 3 — Best-effort sonrası işler
- ledger write
- reaction side-effects
- bazı secondary projection’lar
Failure Matrix
| Durum | raw_data | streams | measurement | runtime state | ledger | Sonuç |
|---|---|---|---|---|---|---|
| invalid JSON | var | yok | yok | yok | yok | journal kalır |
| schema fail | var | yok | yok | yok | yok | journal kalır |
| duplicate | var | yok | yok | yok | yok | duplicate journal kalır |
| stream create fail | var | rollback | yok | yok | yok | raw failed işaretlenir |
| measurement fail | var | rollback | rollback | rollback | yok | raw failed işaretlenir |
| runtime fail | var | rollback | rollback | rollback | yok | raw failed işaretlenir |
| ledger fail | var | var | var | var | failed | accepted event korunur |
Sonuç
Telemetry lifecycle modeli, Cınga backend içindeki accepted veri akışının veritabanı sözleşmesini tanımlar. Burada raw_data giriş jurnali, streams accepted event, measurement.* enerji odaklı typed persistence, measurements generic persistence, runtime tabloları latest state, ledger_transactions ise best-effort integrity trail olarak konumlanır.
Bu ayrım, kod yazarken hangi veri hangi tabloda doğar, hangi veri rollback olur, hangisi tarihsel kanıt olarak kalır ve hangisi yalnız en son durumu taşır sorularına net cevap üretir.