2016-01-18 11 views
25

मेरे पास एक सुरक्षा कैमरा एनवीआर के मेटाडेटा का प्रतिनिधित्व करने वाला डेटाबेस है। वीडियो के हर 1-मिनट खंड के लिए 26-बाइट recording पंक्ति है। (यदि आप उत्सुक हैं, तो डिज़ाइन दस्तावेज़ प्रगति पर है here।) मेरी डिज़ाइन सीमा 8 कैमरे, 1 वर्ष (~ 4 मिलियन पंक्तियां, प्रति कैमरा आधे मिलियन) हैं। मैंने प्रदर्शन का परीक्षण करने के लिए कुछ डेटा तैयार किया है।क्या यह SQLite क्वेरी बहुत तेज हो सकती है?

select 
    recording.start_time_90k, 
    recording.duration_90k, 
    recording.video_samples, 
    recording.sample_file_bytes, 
    recording.video_sample_entry_id 
from 
    recording 
where 
    camera_id = ? 
order by 
    recording.start_time_90k; 

कि बस, एक कैमरा के लिए सभी डेटा को स्कैन अन्य कैमरों और आदेश को छान के लिए एक सूचकांक उपयोग कर रहा है: इस क्वेरी मेरी अपेक्षा से धीमी है। सूचकांक इस तरह दिखता है:

create index recording_camera_start on recording (camera_id, start_time_90k); 

explain query plan अपेक्षा के अनुरूप दिखता है:

0|0|0|SEARCH TABLE recording USING INDEX recording_camera_start (camera_id=?) 

पंक्तियों काफी छोटे हैं।

$ sqlite3_analyzer duplicated.db 
... 

*** Table RECORDING w/o any indices ******************************************* 

Percentage of total database...................... 66.3% 
Number of entries................................. 4225560 
Bytes of storage consumed......................... 143418368 
Bytes of payload.................................. 109333605 76.2% 
B-tree depth...................................... 4 
Average payload per entry......................... 25.87 
Average unused bytes per entry.................... 0.99 
Average fanout.................................... 94.00 
Non-sequential pages.............................. 1   0.0% 
Maximum payload per entry......................... 26 
Entries that use overflow......................... 0   0.0% 
Index pages used.................................. 1488 
Primary pages used................................ 138569 
Overflow pages used............................... 0 
Total pages used.................................. 140057 
Unused bytes on index pages....................... 188317  12.4% 
Unused bytes on primary pages..................... 3987216  2.8% 
Unused bytes on overflow pages.................... 0 
Unused bytes on all pages......................... 4175533  2.9% 

*** Index RECORDING_CAMERA_START of table RECORDING *************************** 

Percentage of total database...................... 33.7% 
Number of entries................................. 4155718 
Bytes of storage consumed......................... 73003008 
Bytes of payload.................................. 58596767 80.3% 
B-tree depth...................................... 4 
Average payload per entry......................... 14.10 
Average unused bytes per entry.................... 0.21 
Average fanout.................................... 49.00 
Non-sequential pages.............................. 1   0.001% 
Maximum payload per entry......................... 14 
Entries that use overflow......................... 0   0.0% 
Index pages used.................................. 1449 
Primary pages used................................ 69843 
Overflow pages used............................... 0 
Total pages used.................................. 71292 
Unused bytes on index pages....................... 8463   0.57% 
Unused bytes on primary pages..................... 865598  1.2% 
Unused bytes on overflow pages.................... 0 
Unused bytes on all pages......................... 874061  1.2% 

... 

मैं (एक पूरे वर्ष के हो सकता है एक बार में केवल एक महीने के बजाय) कुछ इस तरह हर बार आप किसी विशेष वेबपृष्ठ मारा जाता है चलाने के लिए करना चाहते हैं, तो मैं यह काफी तेजी से होना चाहता हूँ। लेकिन मेरे लैपटॉप पर, यह एक सेकंड का अधिकतर लेता है, और रास्पबेरी पीआई 2 पर मैं समर्थन करना चाहता हूं, यह बहुत धीमा है। टाइम्स (सेकेंड में) नीचे; यह सीपीयू बाध्य है (उपयोगकर्ता + sys समय ~ = वास्तविक समय):

laptop$ time ./bench-profiled 
trial 0: time 0.633 sec 
trial 1: time 0.636 sec 
trial 2: time 0.639 sec 
trial 3: time 0.679 sec 
trial 4: time 0.649 sec 
trial 5: time 0.642 sec 
trial 6: time 0.609 sec 
trial 7: time 0.640 sec 
trial 8: time 0.666 sec 
trial 9: time 0.715 sec 
... 
PROFILE: interrupts/evictions/bytes = 1974/489/72648 

real 0m20.546s 
user 0m16.564s 
sys  0m3.976s 
(This is Ubuntu 15.10, SQLITE_VERSION says "3.8.11.1") 

raspberrypi2$ time ./bench-profiled 
trial 0: time 6.334 sec 
trial 1: time 6.216 sec 
trial 2: time 6.364 sec 
trial 3: time 6.412 sec 
trial 4: time 6.398 sec 
trial 5: time 6.389 sec 
trial 6: time 6.395 sec 
trial 7: time 6.424 sec 
trial 8: time 6.391 sec 
trial 9: time 6.396 sec 
... 
PROFILE: interrupts/evictions/bytes = 19066/2585/43124 

real 3m20.083s 
user 2m47.120s 
sys 0m30.620s 
(This is Raspbian Jessie; SQLITE_VERSION says "3.8.7.1") 

मैं संभावना denormalized डेटा किसी प्रकार का कर खत्म कर देंगे, लेकिन पहले मैं अगर मैं इस सरल प्रश्न प्राप्त कर सकते हैं देखना चाहते हैं प्रदर्शन करने के साथ ही यह कर सकते हैं। मेरा बेंचमार्क बहुत आसान है; यह पहले से बयान तैयार करता है और उसके बाद इस पर लूप:

void Trial(sqlite3_stmt *stmt) { 
    int ret; 
    while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) ; 
    if (ret != SQLITE_DONE) { 
    errx(1, "sqlite3_step: %d (%s)", ret, sqlite3_errstr(ret)); 
    } 
    ret = sqlite3_reset(stmt); 
    if (ret != SQLITE_OK) { 
    errx(1, "sqlite3_reset: %d (%s)", ret, sqlite3_errstr(ret)); 
    } 
} 

मैं gperftools के साथ एक सीपीयू प्रोफाइल बनाया है। छवि:

CPU profile graph

$ google-pprof bench-profiled timing.pprof 
Using local file bench-profiled. 
Using local file timing.pprof. 
Welcome to pprof! For help, type 'help'. 
(pprof) top 10 
Total: 593 samples 
    154 26.0% 26.0%  377 63.6% sqlite3_randomness 
    134 22.6% 48.6%  557 93.9% sqlite3_reset 
     83 14.0% 62.6%  83 14.0% __read_nocancel 
     61 10.3% 72.8%  61 10.3% sqlite3_strnicmp 
     41 6.9% 79.8%  46 7.8% sqlite3_free_table 
     26 4.4% 84.1%  26 4.4% sqlite3_uri_parameter 
     25 4.2% 88.4%  25 4.2% llseek 
     13 2.2% 90.6%  121 20.4% sqlite3_db_config 
     12 2.0% 92.6%  12 2.0% __pthread_mutex_unlock_usercnt (inline) 
     10 1.7% 94.3%  10 1.7% __GI___pthread_mutex_lock 

यह काफी अजीब देने के लिए मुझे आशा है कि यह सुधार किया जा सकता लग रहा है। शायद मैं कुछ गूंगा कर रहा हूँ। मैं sqlite3_randomness और sqlite3_strnicmp संचालन के लिए विशेष रूप से उलझन में हूँ:

  • डॉक्स कहना sqlite3_randomness कुछ परिस्थितियों में rowids डालने के लिए प्रयोग किया जाता है, लेकिन मैं सिर्फ एक का चयन क्वेरी कर रहा हूँ। अब इसका इस्तेमाल क्यों किया जाएगा? Skimming sqlite3 स्रोत कोड से, मुझे लगता है कि यह sqlite3ColumnsFromExprList के लिए चयन में उपयोग किया जाता है लेकिन ऐसा लगता है कि कथन तैयार करते समय ऐसा होता है। मैं इसे एक बार कर रहा हूं, भाग में बेंचमार्क नहीं किया जा रहा है।
  • strnicmp केस-असंवेदनशील स्ट्रिंग तुलना के लिए है। लेकिन इस तालिका में हर क्षेत्र एक पूर्णांक है। यह इस समारोह का उपयोग क्यों करेगा? इसकी तुलना क्या है?
  • और सामान्य रूप से, मुझे नहीं पता कि sqlite3_reset महंगा क्यों होगा या इसे sqlite3_step से क्यों कहा जाएगा।

स्कीमा:

-- Each row represents a single recorded segment of video. 
-- Segments are typically ~60 seconds; never more than 5 minutes. 
-- Each row should have a matching recording_detail row. 
create table recording (
    id integer primary key, 
    camera_id integer references camera (id) not null, 

    sample_file_bytes integer not null check (sample_file_bytes > 0), 

    -- The starting time of the recording, in 90 kHz units since 
    -- 1970-01-01 00:00:00 UTC. 
    start_time_90k integer not null check (start_time_90k >= 0), 

    -- The duration of the recording, in 90 kHz units. 
    duration_90k integer not null 
     check (duration_90k >= 0 and duration_90k < 5*60*90000), 

    video_samples integer not null check (video_samples > 0), 
    video_sync_samples integer not null check (video_samples > 0), 
    video_sample_entry_id integer references video_sample_entry (id) 
); 

मैं अपने परीक्षण डेटा + परीक्षण कार्यक्रम को tarred है; आप इसे here डाउनलोड कर सकते हैं।


संपादित करें 1:

int sqlite3_step(sqlite3_stmt *pStmt){ 
    int rc = SQLITE_OK;  /* Result from sqlite3Step() */ 
    int rc2 = SQLITE_OK;  /* Result from sqlite3Reprepare() */ 
    Vdbe *v = (Vdbe*)pStmt; /* the prepared statement */ 
    int cnt = 0;    /* Counter to prevent infinite loop of reprepares */ 
    sqlite3 *db;    /* The database connection */ 

    if(vdbeSafetyNotNull(v)){ 
    return SQLITE_MISUSE_BKPT; 
    } 
    db = v->db; 
    sqlite3_mutex_enter(db->mutex); 
    v->doingRerun = 0; 
    while((rc = sqlite3Step(v))==SQLITE_SCHEMA 
     && cnt++ < SQLITE_MAX_SCHEMA_RETRY){ 
    int savedPc = v->pc; 
    rc2 = rc = sqlite3Reprepare(v); 
    if(rc!=SQLITE_OK) break; 
    sqlite3_reset(pStmt); 
    if(savedPc>=0) v->doingRerun = 1; 
    assert(v->expired==0); 
    } 

ऐसा लगता है कि sqlite3_step की तरह कॉल sqlite3_reset स्कीमा परिवर्तन पर:

आह, SQLite कोड के माध्यम से देख, मैं एक सुराग देखते हैं। (FAQ entry) मैं क्यों वहाँ एक स्कीमा परिवर्तन के बाद से मेरी बयान तैयार किया गया था, हालांकि होगा नहीं पता ...


संपादित करें 2:

मैं SQLite 3.10.1 डाउनलोड की गई "amalgation "और डीबगिंग प्रतीकों के साथ इसके खिलाफ संकलित। मुझे अब एक बहुत अलग प्रोफ़ाइल मिलती है जो अजीब नहीं लगती है, लेकिन यह कोई तेज़ नहीं है। शायद पहले देखे गए अजीब परिणाम समान कोड फोल्डिंग या कुछ के कारण थे।

enter image description here


संपादित करें 3:

नीचे बेन संकुल अनुक्रमणिका समाधान की कोशिश कर रहा, यह तेजी से 3.6x बारे में है। मुझे लगता है कि यह सबसे अच्छा है कि मैं इस प्रश्न के साथ करने जा रहा हूं। SQLite का सीपीयू प्रदर्शन मेरे लैपटॉप पर ~ 700 एमबी/एस है। इसकी वर्चुअल मशीन या कुछ ऐसे के लिए एक जेआईटी कंपाइलर का उपयोग करने के लिए इसे पुनः लिखने के लिए, मैं कोई बेहतर काम नहीं कर रहा हूं। विशेष रूप से, मुझे लगता है कि मैंने अपनी पहली प्रोफ़ाइल पर देखी गई विचित्र कॉल वास्तव में नहीं हो रही थीं; जीसीसी ने अनुकूलन या कुछ के कारण भ्रामक डीबग जानकारी लिखी होगी।

भले ही सीपीयू प्रदर्शन में सुधार किया जाए, फिर भी मेरे स्टोरेज से अधिक थ्रूपुट ठंडा पढ़ने पर कर सकता है, और मुझे लगता है कि यह पीआई पर भी सच है (जिसमें एसडी कार्ड के लिए सीमित यूएसबी 2.0 बस है) ।

$ time ./bench 
sqlite3 version: 3.10.1 
trial 0: realtime 0.172 sec cputime 0.172 sec 
trial 1: realtime 0.172 sec cputime 0.172 sec 
trial 2: realtime 0.175 sec cputime 0.175 sec 
trial 3: realtime 0.173 sec cputime 0.173 sec 
trial 4: realtime 0.182 sec cputime 0.182 sec 
trial 5: realtime 0.187 sec cputime 0.187 sec 
trial 6: realtime 0.173 sec cputime 0.173 sec 
trial 7: realtime 0.185 sec cputime 0.185 sec 
trial 8: realtime 0.190 sec cputime 0.190 sec 
trial 9: realtime 0.192 sec cputime 0.192 sec 
trial 10: realtime 0.191 sec cputime 0.191 sec 
trial 11: realtime 0.188 sec cputime 0.188 sec 
trial 12: realtime 0.186 sec cputime 0.186 sec 
trial 13: realtime 0.179 sec cputime 0.179 sec 
trial 14: realtime 0.179 sec cputime 0.179 sec 
trial 15: realtime 0.188 sec cputime 0.188 sec 
trial 16: realtime 0.178 sec cputime 0.178 sec 
trial 17: realtime 0.175 sec cputime 0.175 sec 
trial 18: realtime 0.182 sec cputime 0.182 sec 
trial 19: realtime 0.178 sec cputime 0.178 sec 
trial 20: realtime 0.189 sec cputime 0.189 sec 
trial 21: realtime 0.191 sec cputime 0.191 sec 
trial 22: realtime 0.179 sec cputime 0.179 sec 
trial 23: realtime 0.185 sec cputime 0.185 sec 
trial 24: realtime 0.190 sec cputime 0.190 sec 
trial 25: realtime 0.189 sec cputime 0.189 sec 
trial 26: realtime 0.182 sec cputime 0.182 sec 
trial 27: realtime 0.176 sec cputime 0.176 sec 
trial 28: realtime 0.173 sec cputime 0.173 sec 
trial 29: realtime 0.181 sec cputime 0.181 sec 
PROFILE: interrupts/evictions/bytes = 547/178/24592 

real 0m5.651s 
user 0m5.292s 
sys  0m0.356s 

मुझे कुछ denormalized डेटा रखना पड़ सकता है। सौभाग्य से, मैं सोच रहा हूं कि मैं इसे अपने एप्लिकेशन की रैम में रख सकता हूं क्योंकि यह बहुत बड़ा नहीं होगा, स्टार्टअप को आश्चर्यजनक रूप से तेज़ नहीं होना चाहिए, और केवल एक ही प्रक्रिया डेटाबेस को लिखती है।

+3

आपके प्रश्न में इतने सारे शोध प्रयास करने के लिए धन्यवाद! क्या आप बता सकते हैं कि आप सीपीयू-बाध्य हैं या आईओ-बाध्य हैं? क्या आप [रास्पबेरी पीआई पर कक्षा 10 एसडी कार्ड] का उपयोग कर रहे हैं (http://raspberrypi.stackexchange.com/q/12191/27703)? –

+2

धन्यवाद! और एक महत्वपूर्ण सवाल मैं जवाब देना भूल गया। यह दोनों प्रणालियों पर सीपीयू-बाध्य है। मैंने इसे दिखाने के लिए उपरोक्त "समय" आउटपुट जोड़ा। और मैं कक्षा 10 एसडी कार्ड का उपयोग कर रहा हूं: http://www.amazon.com/gp/product/B010Q588D4?psc=1&redirect=true&ref_=od_aui_detailpages00 –

+2

बहुत बढ़िया सवाल! विवरण के इस स्तर के साथ आपको शायद एसक्लाइट-उपयोगकर्ता एमएल पर भी पोस्ट करना चाहिए। – viraptor

उत्तर

2

आपको क्लस्टर्ड इंडेक्स की आवश्यकता है, या यदि आप SQLite का एक संस्करण उपयोग कर रहे हैं जो एक कवर इंडेक्स का समर्थन नहीं करता है।

SQLite 3.8.2 और

उपयोग इस SQLite 3.8.2 में ऊपर और ऊपर:

create table recording (
    camera_id integer references camera (id) not null, 

    sample_file_bytes integer not null check (sample_file_bytes > 0), 

    -- The starting time of the recording, in 90 kHz units since 
    -- 1970-01-01 00:00:00 UTC. 
    start_time_90k integer not null check (start_time_90k >= 0), 

    -- The duration of the recording, in 90 kHz units. 
    duration_90k integer not null 
     check (duration_90k >= 0 and duration_90k < 5*60*90000), 

    video_samples integer not null check (video_samples > 0), 
    video_sync_samples integer not null check (video_samples > 0), 
    video_sample_entry_id integer references video_sample_entry (id), 

    --- here is the magic 
    primary key (camera_id, start_time_90k) 
) WITHOUT ROWID; 

इससे पहले संस्करण

SQLite के पिछले संस्करणों आप इस का उपयोग कर सकते में एक कवर सूचकांक बनाने के लिए चीज की तरह।यह प्रत्येक पंक्ति के लिए एक अलग पृष्ठ को लाना परहेज, SQLite सूचकांक से डेटा मान खींचने के लिए अनुमति चाहिए: (कि की परवाह किए बिना

create index recording_camera_start on recording (
    camera_id, start_time_90k, 
    sample_file_bytes, duration_90k, video_samples, video_sync_samples, video_sample_entry_id 
); 

चर्चा

लागत आईओ होने की संभावना है आप यह कहा नहीं था) क्योंकि याद है कि आईओ को सीपीयू की आवश्यकता है क्योंकि डेटा को बस से और उसके लिए कॉपी किया जाना चाहिए।

क्लस्टर्ड इंडेक्स के बिना, पंक्तियों को एक पंक्ति के साथ डाला जाता है, और किसी भी समझदार क्रम में नहीं हो सकता है। इसका मतलब है कि आपके द्वारा अनुरोध की जाने वाली प्रत्येक 26 बाइट पंक्ति के लिए, सिस्टम को एसडी कार्ड से 4 केबी पृष्ठ प्राप्त करना पड़ सकता है - जो बहुत अधिक ओवरहेड है।

8 कैमरों की एक सीमा के साथ, id पर एक साधारण क्लस्टर्ड इंडेक्स यह सुनिश्चित करने के लिए कि वे डाले गए क्रम में डिस्क पर दिखाई दें, संभवतः यह सुनिश्चित करके आपको 10x गति वृद्धि होगी कि प्राप्त पृष्ठ में अगले 10-20 पंक्तियां होंगी आवश्यक होना।

कैमरे और समय दोनों पर क्लस्टर्ड इंडेक्स यह सुनिश्चित करना चाहिए कि प्रत्येक पृष्ठ पर 100 या अधिक पंक्तियां हों।

+0

धन्यवाद! दिलचस्प समाधान, और मैंने इसे ऊपर बेंचमार्क किया; यह> 3 एक्स तेज है। 'camera_id, start_time_90k' अद्वितीय नहीं हो सकता है (मुझे यह पसंद है, लेकिन समय कूदता है और ऐसा लगता है, और मेरी प्रणाली को शायद कुछ रिकॉर्डिंग करना और बाद में ऑफसेट को सॉर्ट करना पसंद करना चाहिए)। लेकिन मुझे लगता है कि मैं थोड़ी देर के लिए झुका सकता हूं (एक सेकंड के 1/9 0,000 वें का ऑफसेट क्या है) या उस प्राथमिक कुंजी में तीसरे कॉलम के रूप में "आईडी" को अपनी अनूठी नल इंडेक्स के साथ वापस जोड़ दें। –

+0

@ स्कॉटलैम्ब, मैं आईडी के लिए जाऊंगा। आप घड़ियों के साथ कभी नहीं जानते - वे कभी-कभी पीछे जाते हैं! कम से कम आईडी आपको वास्तविक डालने वाला आदेश देगा ताकि वह खो न जाए। – Ben

संबंधित मुद्दे