أنا أكتب تطبيقًا يحتاج إلى تخزين وتحليل كميات كبيرة من البيانات الكهربائية وبيانات درجة الحرارة.
في الأساس ، أحتاج إلى تخزين كميات كبيرة من قياسات استخدام الكهرباء لكل ساعة للسنوات العديدة الماضية ولسنوات عديدة قادمة لعشرات الآلاف من المواقع ثم تحليل البيانات بطريقة غير معقدة للغاية.
المعلومات التي أحتاج إلى تخزينها (الآن) هي معرف الموقع والطابع الزمني (التاريخ والوقت) ودرجة الحرارة واستخدام الكهرباء.
حول كمية البيانات التي يجب تخزينها ، هذا تقريب ، ولكن شيئًا على هذا المنوال:
20000 موقعًا ، و 720 سجلًا شهريًا (قياسات كل ساعة ، وحوالي 720 ساعة شهريًا) ، و 120 شهرًا (لمدة 10 سنوات) والعديد من السنوات القادمة. تؤدي الحسابات البسيطة إلى النتائج التالية:
20000 موقع × 720 سجل × 120 شهرًا (10 سنوات مضت) = 17800000 سجل.
هذه هي السجلات السابقة ، سيتم استيراد السجلات الجديدة شهريًا ، بحيث يكون هذا تقريبًا 20000 × 720 = 14400000 سجل جديد شهريًا.
سوف ينمو إجمالي المواقع بشكل مطرد أيضًا.
على جميع هذه البيانات ، ستحتاج إلى تنفيذ العمليات التالية:
ستتم كتابة البيانات شهريًا ، ولكن سيتم قراءتها من قبل مئات المستخدمين (على الأقل) باستمرار ، وبالتالي فإن سرعة القراءة ذات أهمية أكبر.
ليس لدي خبرة في قواعد بيانات NoSQL ولكن من خلال ما جمعته ، فهي أفضل حل لاستخدامه هنا. لقد قرأت على قواعد بيانات NoSQL الأكثر شيوعًا ، ولكن نظرًا لأنها مختلفة تمامًا وتسمح أيضًا ببنية جدول مختلفة تمامًا ، لم أتمكن من تحديد أفضل قاعدة بيانات لاستخدامها.
كانت اختياراتي الرئيسية Cassandra و MongoDB ، ولكن منذ ذلك الحين لدي معرفة محدودة جدًا ولا خبرة حقيقية عندما يتعلق الأمر بالبيانات الكبيرة و NoSQL ، لست متأكدًا تمامًا. قرأت أيضًا أن PostreSQL تتعامل أيضًا هذه الكميات من البيانات بشكل جيد.
أسئلتي هي التالية:
شكرا لك.
هذا هو بالضبط ما أفعله كل يوم ، باستثناء البيانات بدلاً من استخدام بيانات الساعة ، أستخدم بيانات 5 دقائق. أقوم بتنزيل حوالي 200 مليون سجل يوميًا ، لذا فإن المبلغ الذي تتحدث عنه هنا لا يمثل مشكلة. بيانات الخمس دقائق هي حوالي 2 TB في الحجم ولدي بيانات الطقس تعود إلى 50 عامًا على مدار الساعة حسب الموقع. لذا دعني أجب على الأسئلة بناءً على تجربتي:
نصيحة عامة: أقوم بتخزين معظم البيانات بين قاعدتي بيانات ، الأولى هي بيانات السلاسل الزمنية المستقيمة ويتم تطبيعها. قاعدة بياناتي الثانية غير طبيعية وتحتوي على بيانات مجمعة مسبقًا. بالسرعة التي يتمتع بها نظامي ، لست عمياء عن حقيقة أن المستخدمين لا يريدون حتى الانتظار 30 ثانية حتى يتم تحميل التقرير - حتى إذا كنت أفكر شخصيًا في 30 ثانية للتغلب على 2 TB = البيانات سريعة للغاية.
لتوضيح سبب التوصية بتخزين الساعة بشكل منفصل عن التاريخ ، إليك بعض الأسباب التي تجعلني أفعل ذلك بهذه الطريقة:
DATETIME
.كما قلت أعلاه ، كل هذا يعتمد على تجربتي الشخصية ، ودعوني أخبركم ، لقد مرت سنوات قليلة صعبة والعديد من عمليات إعادة التصميم للوصول إلى ما أنا عليه الآن. لا تفعل ما فعلته ، وتعلم من أخطائي وتأكد من إشراك المستخدمين النهائيين لنظامك (أو المطورين ، مؤلفي التقارير ، إلخ ...) عند اتخاذ قرارات بشأن قاعدة بياناتك.
اختبره بنفسك. هذه ليست مشكلة على جهاز كمبيوتر محمول عمره 5 سنوات مع SSD.
EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
SELECT
x::int AS id,
(x::int % 20000)::int AS locid, -- fake location ids in the range of 1-20000
now() AS tsin, -- static timestmap
97.5::numeric(5,2) AS temp, -- static temp
x::int AS usage -- usage the same as id not sure what we want here.
FROM generate_series(1,1728000000) -- for 1.7 billion rows
AS gs(x);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series gs (cost=0.00..15.00 rows=1000 width=4) (actual time=173119.796..750391.668 rows=1728000000 loops=1)
Planning time: 0.099 ms
Execution time: 1343954.446 ms
(3 rows)
لذلك استغرق 22 دقيقة لإنشاء الجدول. إلى حد كبير ، لأن الطاولة بحجم 97 جيجابايت متواضع. بعد ذلك نقوم بإنشاء الفهارس ،
CREATE INDEX ON electrothingy USING brin (tsin);
CREATE INDEX ON electrothingy USING brin (id);
VACUUM ANALYZE electrothingy;
لقد استغرق إنشاء الفهارس وقتًا طويلاً أيضًا. على الرغم من أنهم BRIN ، إلا أنهم 2-3 ميغابايت ويتم تخزينهم بسهولة في ذاكرة الوصول العشوائي. قراءة 96 غيغابايت ليست فورية ، لكنها ليست مشكلة حقيقية لجهاز الكمبيوتر المحمول الخاص بك في عبء العمل الخاص بك.
الآن نستعلم عنها.
explain analyze
SELECT max(temp)
FROM electrothingy
WHERE id BETWEEN 1000000 AND 1001000;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=5245.22..5245.23 rows=1 width=7) (actual time=42.317..42.317 rows=1 loops=1)
-> Bitmap Heap Scan on electrothingy (cost=1282.17..5242.73 rows=993 width=7) (actual time=40.619..42.158 rows=1001 loops=1)
Recheck Cond: ((id >= 1000000) AND (id <= 1001000))
Rows Removed by Index Recheck: 16407
Heap Blocks: lossy=128
-> Bitmap Index Scan on electrothingy_id_idx (cost=0.00..1281.93 rows=993 width=0) (actual time=39.769..39.769 rows=1280 loops=1)
Index Cond: ((id >= 1000000) AND (id <= 1001000))
Planning time: 0.238 ms
Execution time: 42.373 ms
(9 rows)
هنا ننشئ جدولًا يحتوي على طوابع زمنية مختلفة من أجل تلبية طلب الفهرسة والبحث في عمود الطابع الزمني ، يستغرق الإنشاء وقتًا أطول قليلاً لأن to_timestamp(int)
أبطأ بكثير من now()
(التي تم تخزينه مؤقتًا للمعاملة)
EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
SELECT
x::int AS id,
(x::int % 20000)::int AS locid,
-- here we use to_timestamp rather than now(), we
-- this calculates seconds since Epoch using the gs(x) as the offset
to_timestamp(x::int) AS tsin,
97.5::numeric(5,2) AS temp,
x::int AS usage
FROM generate_series(1,1728000000)
AS gs(x);
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series gs (cost=0.00..17.50 rows=1000 width=4) (actual time=176163.107..5891430.759 rows=1728000000 loops=1)
Planning time: 0.607 ms
Execution time: 7147449.908 ms
(3 rows)
الآن يمكننا تشغيل استعلام على قيمة الطابع الزمني بدلاً من ذلك ،،
explain analyze
SELECT count(*), min(temp), max(temp)
FROM electrothingy WHERE tsin BETWEEN '1974-01-01' AND '1974-01-02';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=296073.83..296073.84 rows=1 width=7) (actual time=83.243..83.243 rows=1 loops=1)
-> Bitmap Heap Scan on electrothingy (cost=2460.86..295490.76 rows=77743 width=7) (actual time=41.466..59.442 rows=86401 loops=1)
Recheck Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
Rows Removed by Index Recheck: 18047
Heap Blocks: lossy=768
-> Bitmap Index Scan on electrothingy_tsin_idx (cost=0.00..2441.43 rows=77743 width=0) (actual time=40.217..40.217 rows=7680 loops=1)
Index Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
Planning time: 0.140 ms
Execution time: 83.321 ms
(9 rows)
نتيجة:
count | min | max
-------+-------+-------
86401 | 97.50 | 97.50
(1 row)
لذا في 83.321 مللي ثانية يمكننا تجميع 86.401 تسجيلًا في جدول يحتوي على 1.7 مليار صف. يجب أن يكون ذلك معقولاً.
يعد حساب نهاية الساعة أمرًا سهلاً للغاية أيضًا ، واقطع الطوابع الزمنية لأسفل ثم ببساطة أضف ساعة.
SELECT date_trunc('hour', tsin) + '1 hour' AS tsin,
count(*),
min(temp),
max(temp)
FROM electrothingy
WHERE tsin >= '1974-01-01'
AND tsin < '1974-01-02'
GROUP BY date_trunc('hour', tsin)
ORDER BY 1;
tsin | count | min | max
------------------------+-------+-------+-------
1974-01-01 01:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 02:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 03:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 04:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 05:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 06:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 07:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 08:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 09:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 10:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 11:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 12:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 13:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 14:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 15:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 16:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 17:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 18:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 19:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 20:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 21:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 22:00:00-06 | 3600 | 97.50 | 97.50
1974-01-01 23:00:00-06 | 3600 | 97.50 | 97.50
1974-01-02 00:00:00-06 | 3600 | 97.50 | 97.50
(24 rows)
Time: 116.695 ms
من المهم ملاحظة أنه لا يستخدم فهرسًا على التجميع ، على الرغم من أنه يمكن ذلك. إذا كان هذا هو استعلامك المعتاد ، فمن المحتمل أنك تريد BRIN في date_trunc('hour', tsin)
وهنا تكمن مشكلة صغيرة في أن date_trunc
غير قابل للتغيير ، لذا عليك أولاً لفه لجعله كذلك.
نقطة أخرى مهمة للمعلومات عن PostgreSQL هي أن PG 10 يجلب تقسيم DDL . لذلك ، على سبيل المثال ، يمكنك بسهولة إنشاء أقسام لكل عام. تقسيم قاعدة بياناتك المتواضعة إلى ثانوية صغيرة. عند القيام بذلك ، يجب أن تكون قادرًا على استخدام والحفاظ على فهارس btree بدلاً من BRIN التي ستكون أسرع.
CREATE TABLE electrothingy_y2016 PARTITION OF electrothingy
FOR VALUES FROM ('2016-01-01') TO ('2017-01-01');
أو أيا كان.
يدهشني أنه لم يذكر أحد هنا قياس الأداء - أي حتى @ EvanCarroll جاء مع مساهمته الممتازة!
إذا كنت مكانك ، كنت سأقضي بعض الوقت (ونعم ، أعلم أنها سلعة ثمينة!) لإعداد الأنظمة ، وتشغيل ما تعتقد أنه سيكون (احصل على مدخلات المستخدم النهائي هنا!) ، على سبيل المثال ، 10 استفساراتك الأكثر شيوعًا.
أفكاري الخاصة:
يمكن أن تعمل حلول NoSQL بشكل جيد جدًا في حالات الاستخدام الخاصة ولكنها غالبًا ما تكون غير مرنة بالنسبة لاستعلامات الأقران. للحصول على لقطة مسلية على NoSQL بواسطة Brian Aker - كبير مهندسي MySQL السابق ، انظر هنا !
أتفق مع @ Mr.Brownstone في أن بياناتك مناسبة بشكل واضح لحل علائقي (وهذا الرأي تم تأكيده بواسطة Evan Carroll )!
إذا كنت سألتزم بأي نفقات ، فسيكون ذلك لتقنيتي على القرص! سأقوم بإنفاق أي أموال كنت في حوزتي على NAS أو SAN أو ربما بعض أقراص SSD للاحتفاظ ببيانات مجمعة نادرًا مكتوبة!
أولاً أود أن أنظر إلى ما هو متوفر لدي الآن . قم بإجراء بعض الاختبارات وأظهر النتائج لصناع القرار. لديك بالفعل وكيل على شكل عمل EC ! ولكن ، اختبارًا سريعًا أو اثنين معًا على الأجهزة الخاصة بك سيكون أكثر إقناعًا!
ثم فكر في إنفاق المال! إذا كنت تنفق الأموال ، فابحث عن الأجهزة أولاً بدلاً من البرامج. AFAIK ، يمكنك الاستعانة بتكنولوجيا القرص لفترة تجريبية ، أو الأفضل من ذلك ، عرض مجموعة من بروفات المفاهيم على السحابة.
أول منفذ شخصي خاص بي لمشروع مثل هذا سيكون PostgreSQL. هذا لا يعني أنني سأستبعد حل الملكية ، ولكن قوانين الفيزياء والأقراص هي نفسها للجميع! "Yae cannae beet the قوانين الفيزياء جيم" :-)
إذا لم تكن قد فعلت ذلك بالفعل ، فقم بإلقاء نظرة على سلسلة DBMS ، لأنها محسنة لتخزين البيانات والاستعلام عنها حيث يكون التركيز الأساسي هو نوع التاريخ/الوقت. عادةً ما يتم استخدام قواعد بيانات السلاسل الزمنية لتسجيل البيانات في نطاقات الدقيقة/الثانية/الفرعية الثانية ، لذلك لست متأكدًا مما إذا كانت لا تزال مناسبة لزيادات كل ساعة. ومع ذلك ، يبدو أن هذا النوع من DBMS يستحق النظر فيه. يبدو أن InfluxDB حاليًا هي قاعدة بيانات السلاسل الزمنية الأكثر استخدامًا والأكثر استخدامًا.
من الواضح أن هذه ليست مشكلة NoSQL ، لكنني أقترح أنه في حين أن حل RDBMS سيعمل ، أعتقد أن نهج OLAP سيكون مناسبًا بشكل أفضل نظرًا لنطاقات البيانات المحدودة جدًا المعنية ، أود بشدة اقترح التحقيق في استخدام قاعدة بيانات تستند إلى عمود بدلاً من صف واحد. فكر في الأمر بهذه الطريقة ، فقد يكون لديك 1.7 مليار قطعة من البيانات ، ولكنك لا تزال بحاجة إلى 5 بت فقط لفهرسة كل قيمة محتملة للساعة أو اليوم من الشهر.
لدي خبرة في مجال مشكلة مماثلة حيث يتم استخدام Sybase IQ (الآن SAP IQ) لتخزين ما يصل إلى 300 مليون عدادات في الساعة من بيانات إدارة أداء معدات الاتصالات ، لكني أشك في وجود ميزانية لهذا النوع من الحلول. في ساحة مفتوحة المصدر ، يعد MariaDB ColumnStore مرشحًا واعدًا للغاية ، لكني أوصي أيضًا بالتحقيق في MonetDB.
نظرًا لأن أداء طلب البحث هو محرك رئيسي لك ، فكر في كيفية صياغة الاستعلامات. هذا هو المكان OLAP و RDBMS تظهر اختلافاتهما الكبرى: - مع OLAP تطبيع لأداء الاستعلام ، وليس لتقليل التكرار ، أو تقليل التخزين أو حتى لفرض الاتساق لذا بالإضافة إلى الطابع الزمني الأصلي (هل تذكرت التقاط المنطقة الزمنية التي آملها؟) يحتوي على حقل منفصل للطابع الزمني UTC ، وآخر للتاريخ والوقت ، ومع ذلك أكثر للسنة والشهر واليوم والساعة ، دقيقة وإزاحة UTC. إذا كانت لديك معلومات إضافية حول المواقع ، فلا تتردد في الاحتفاظ بذلك في جدول موقع منفصل يمكن البحث عنه عند الطلب ولا تتردد في الاحتفاظ بمفتاح هذا الجدول في سجلك الرئيسي ولكن احتفظ باسم الموقع بالكامل في الجدول الرئيسي الخاص بك أيضًا ، بعد كل شيء ، لا تزال جميع المواقع المحتملة تستغرق 10 بتات فقط للفهرسة وكل مرجع لا يتعين عليك اتباعه للحصول على البيانات التي يتم الإبلاغ عنها هو توفير الوقت في الاستعلام الخاص بك.
كاقتراح أخير ، استخدم جداول منفصلة للبيانات المجمعة الشائعة واستخدم المهام المجمعة لملئها ، وبهذه الطريقة لن تضطر إلى تكرار التمرين لكل تقرير يستخدم قيمة مجمعة ويجعل الاستعلامات التي تقارن الحالية بالبيانات السابقة أو التاريخية تاريخي إلى تاريخي أسهل بكثير وأسرع بكثير.